Compare commits

...

259 Commits

Author SHA1 Message Date
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
Atsushi Watanabe
8a4e0779d7 Add test for vpx input image size change 2021-01-08 15:08:13 +09:00
Renovate Bot
d222ff3d74 Update module gen2brain/malgo to v0.10.29
Generated by Renovate Bot
2021-01-07 21:29:45 -08:00
Renovate Bot
b9bb4fdc34 Update module pion/webrtc/v3 to v3.0.3
Generated by Renovate Bot
2021-01-07 21:29:16 -08:00
Renovate Bot
cc823958e1 Update golang.org/x/image commit hash to 35266b9
Generated by Renovate Bot
2021-01-08 13:25:23 +09:00
Renovate Bot
64f39187b8 Update module google/uuid to v1.1.4
Generated by Renovate Bot
2021-01-08 13:24:50 +09:00
f-fl0
c56cc487a3 Fix VPX_CODEC_INVALID_PARAM on resolution change (#283)
* Fix VPX_CODEC_INVALID_PARAM on resolution change
  - When chaning resolution to a larger one,
    vpx_codec_enc_config_set fails with VPX_CODEC_INVALID_PARAM.
  - Creating a new codec object seems to be necessary.
* Address PR comments
  - Free encoder old codec.
2021-01-08 12:19:14 +09:00
Lukas Herman
d86fc4a3e9 Switch default frame format to I420
I420 format is a common format that's required by encoders. Therefore,
the video pipeline is significantly faster than other formats since
there's no format conversion.
2020-12-21 01:17:41 -05:00
Lukas Herman
2f21d9e738 Add example compilation test to CI
Changes:
  * Make codec build command now is prefixed with "build"
  * A new "test" command to make
  * Add a Makefile for examples
2020-12-19 15:41:55 -05:00
Lukas Herman
3316476b30 Fix uncompiled C codes due to undefined std 2020-12-19 15:41:55 -05:00
Lukas Herman
0b1a19f343 Enhance build system by switching to Makefile
By switching to Makefile, parallel builds and single build are now
possible.

Examples:

  Parallel Build:
    make -j

  Single Build:
    make opus-darwin-x64

Also, since Makefile has a dependency change detection feature, the
build time is reduced significantly when there are only a few things that
change, Makefile will not rebuild everything unnecessarily.
2020-12-18 22:55:45 -05:00
Lukas Herman
ad37c826b9 Add license check to CI 2020-12-18 18:51:29 -05:00
Lukas Herman
d84d0a3b0c Upgrade pion/webrtc from v2 to v3
With webrtc v3, users no longer need to bind or unbind manually anymore.

Changes:
  * Switch from webrtc.RTPCodec to webrtc.RTPCodecParameters
  * Fix broken examples after the upgrade
  * NewRTPReader now accepts ssrc as a parameter
  * Track interface now fulfills webrtc.TrackLocal requirements
2020-12-18 14:33:42 -05:00
Lukas Herman
c068f1176d Ignore example binaries 2020-12-18 14:33:42 -05:00
Lukas Herman
76908aca89 Disable stack protection in opus 2020-12-17 19:01:19 -05:00
Lukas Herman
f3ac1f5480 Add static-linked opus option
Changes:
  * Remove hraban/opus dependency
  * Add multiple static-linked libopus binaries
2020-12-16 19:04:12 -05:00
Lukas Herman
6280b450ba Fix missing sampling rate in audiotest 2020-12-16 19:04:12 -05:00
Lukas Herman
db0884a77c Fix invalid format type panic 2020-12-16 19:04:12 -05:00
Lukas Herman
1b5203d3a0 Enhance cross-platform build system (#266)
With this new build system, it'll make it easier to compile third-party
libraries statically to multiple platforms.
2020-12-15 14:13:12 -05:00
renovate[bot]
f472618b71 Update module pion/rtp to v1.6.2 (#265)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2020-12-14 22:08:56 -05:00
renovate[bot]
7f4d1bc5ad Update module gen2brain/malgo to v0.10.27 (#259)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2020-12-14 22:08:29 -05:00
Lukas Herman
97046bc6ec Add NewEncodedIOReader (#263)
Changes:
  * [BREAKING CHANGE] NewEncodedReader is renamed to NewEncodedIOReader
  * NewEncodedReader now returns a non-standard buffer reader to give
  more meta data such as sample count
2020-12-14 22:07:57 -05:00
f-fl0
7bcc9111f4 Tolerate video frame rate variation in properties detector (#261)
Changes:
* Add argument to tolerate some FPS variations.
   * Update wrapper function.
   * Update tests.
* Add test about frame rate change tolerance.
   * Verify the onChange function is not called when the frame rate change
is within the the specified tolerance.
* Update test about frame rate variation detection
   * Create dedicated throttle transform function to slow down after a specific amount of time.
* Remove unnecessary code.
2020-12-11 12:36:55 -08:00
Atsushi Watanabe
044b5566d1 Don't cancel other matrix tests on fail 2020-12-11 15:28:00 -05:00
Atsushi Watanabe
7f41f9b8df Skip bitrate measure test on darwin 2020-12-11 15:28:00 -05:00
Lukas Herman
5d5001d0b4 Bring back x11 adapter for Linux build only
Original x11 adapter uses ~40% less CPU usage than kbinani/screenshot.
Ideally, we should migrate 100% to kbinani/screenshot. But, the CPU
usage difference is substantial. In the future, we should look deeper
into kbinani/screenshot and try to improve it.
2020-11-23 20:36:17 -05:00
Lukas Herman
c734c53b00 Revert "Create codeql-analysis.yml"
This reverts commit ce032411a7.
2020-11-21 14:38:31 -08:00
Lukas Herman
92ac89a620 Update README.md 2020-11-21 14:11:35 -08:00
Lukas Herman
ce032411a7 Create codeql-analysis.yml 2020-11-21 14:11:00 -08:00
Lukas Herman
3ea7120130 Remove some hardcoded microphone metadata
Changes:
  * Use malgo.DeviceInfo to get the microphone metadata
  * Move static buffering to the driver layer
2020-11-21 14:07:32 -08:00
Lukas Herman
282d0a4fb4 Migrate to kbinani/screenshot for screen adapter
https://github.com/kbinani/screenshot is a library to capture desktop
screen. It is cgo free for Windows, Linux, FreeBSD, OpenBSD, NetBSD, and
Solaris.
2020-11-21 13:40:35 -08:00
Lukas Herman
356eee19ce Remove pkg-config requirement for mmal 2020-11-21 20:57:26 +00:00
Lukas Herman
02d0cd3f44 Prioritize /dev/video0 on Linux camera 2020-11-21 12:44:42 -08:00
Lukas Herman
a8dbecff30 Fix crash in x264 encoder close 2020-11-21 11:42:21 -08:00
Lukas Herman
273457b370 Remove git LFS 2020-11-17 11:41:09 -08:00
Lukas Herman
9846a7eb67 Remove demo.gif to avoid big storage usage 2020-11-17 11:37:28 -08:00
Renovate Bot
b2e72af884 Update module pion/rtp to v1.6.1
Generated by Renovate Bot
2020-11-16 21:14:08 -08:00
Renovate Bot
b8dd3811ac Update module gen2brain/malgo to v0.10.24
Generated by Renovate Bot
2020-11-16 08:38:46 -08:00
Lukas Herman
c1958b62a2 Update README.md 2020-11-09 23:21:10 -08:00
Lukas Herman
ea90f86abd Update README.md 2020-11-09 23:19:50 -08:00
Lukas Herman
716da16e4a Add NewEncodedReeader API
Changes:
  * Add NewEncodedReeader method to Track interface
  * Add video archival example
2020-11-09 23:17:48 -08:00
Lukas Herman
1550a68003 Fix incorrect audio sampler 2020-11-09 22:24:42 -08:00
Lukas Herman
d65170dfe3 Fix wrong duration in vpx due to buffer usage
VPX doesn't allow 0 duration. If 0 is given, vpx_codec_encode will fail with VPX_CODEC_INVALID_PARAM.
0 duration is possible because mediadevices first gets the frame meta data by reading from the source,
and consequently the codec will read the first frame from the buffer. This means the first frame won't
have a pause to the second frame, which means if the delay is <1 ms (vpx duration resolution), duration
is going to be 0.
2020-11-09 22:16:23 -08:00
Lukas Herman
4057524bf0 Update README 2020-11-08 14:16:26 -08:00
Lukas Herman
8dd84b269c Use x264 instead of vp8 for rtp example 2020-11-07 21:04:26 -08:00
Lukas Herman
a73b1922ed Add codec installation steps to webrtc example 2020-11-07 20:54:14 -08:00
Lukas Herman
11aea3eb85 Update webrtc example README 2020-11-07 20:50:24 -08:00
Lukas Herman
cd49cd9910 Update http example README 2020-11-07 20:44:43 -08:00
Lukas Herman
6900da9a5e Update README.md 2020-11-07 20:42:53 -08:00
Lukas Herman
0a1944dc77 Update README 2020-11-06 14:41:06 -08:00
Lukas Herman
c3100355e5 Create README for faacedetection 2020-11-04 22:39:04 -08:00
Lukas Herman
b35246730d Update http example to use dynamic ip and port 2020-11-04 22:37:31 -08:00
Lukas Herman
0c61817369 Create README.md 2020-11-04 22:33:51 -08:00
Lukas Herman
2fe26ea1f7 Update main.go 2020-11-04 20:53:48 -08:00
Lukas Herman
9d98eb8aaf Go mod tidy 2020-11-03 07:27:32 +00:00
Lukas Herman
3ea35bebab Fix broken mediastream unit test 2020-11-02 23:05:59 -08:00
Lukas Herman
83c08e6c5f Recreate facedetection example with the new APIs 2020-11-02 23:04:58 -08:00
Lukas Herman
2f17017450 Rename rtp-send to rtp 2020-11-02 22:31:46 -08:00
Lukas Herman
7cbda134b0 Add NewRTPReader to Track interface 2020-11-02 22:28:01 -08:00
Lukas Herman
115be126ec Add documentation around Reader interfaces 2020-11-02 22:22:19 -08:00
Lukas Herman
79dcb4f1af Add video and audio RTP readers 2020-11-02 22:12:43 -08:00
Lukas Herman
5db4007e73 Enable non-webrtc sampler 2020-11-01 15:31:36 -08:00
Lukas Herman
77ebcecac6 Add codec selector by string names 2020-11-01 15:14:08 -08:00
Renovate Bot
a0d0949954 Update golang.org/x/sys commit hash to 201ba4d
Generated by Renovate Bot
2020-11-01 01:12:10 -07:00
Lukas Herman
f396092609 Default high profile for x264 when possible 2020-11-01 01:08:03 -07:00
Lukas Herman
ee6cf08c44 Use miniaudio in favor of implementing ourselves
miniaudio supports various backends. This will help reducing code
surface
2020-11-01 00:48:54 -07:00
Lukas Herman
6a211aa19f Use x264 as default in example 2020-11-01 00:38:42 -07:00
Lukas Herman
b089610c27 Fix incorrect audio latency 2020-11-01 00:32:20 -07:00
Lukas Herman
1d34ec9c5d Fix broken vpx example 2020-10-31 23:55:46 -07:00
Lukas Herman
7bd3efc8b7 Fix broken conditional build 2020-10-31 11:19:02 -07:00
Lukas Herman
8396fd7aac Add an end-to-end benchmark 2020-10-31 10:35:53 -07:00
Lukas Herman
3787158dba WIP 2020-10-31 01:12:14 -07:00
Lukas Herman
640eeb0cc0 Fix panic when printing non-reference types 2020-10-30 17:50:55 -07:00
Lukas Herman
16ceb45c25 Replace <nil> -> any in prop pretty format 2020-10-30 17:42:07 -07:00
Lukas Herman
c98b3b0909 Enhance driver discovery logging 2020-10-30 17:32:40 -07:00
Lukas Herman
e6c98a844f Remove unused fmt 2020-10-30 10:01:07 -07:00
Lukas Herman
2a70c031b8 Remove unwanted logging 2020-10-30 09:04:51 -07:00
Lukas Herman
047013be95 Fix division by 0 when sample rate is not filled 2020-10-30 08:58:46 -07:00
Lukas Herman
765318feb6 Fix webrtc example 2020-10-30 01:19:03 -07:00
Lukas Herman
af6d31fde5 Revert go.mod 2020-10-30 00:37:38 -07:00
Lukas Herman
2f5e4ee914 New mediadevices design
Changelog:
  * Better support for non-webrtc use cases
  * Enable multiple readers
  * Enhance codec selectors
  * Update APIs to reflect on the new v3 webrtc design
  * Cleaner APIs
2020-10-30 00:33:55 -07:00
Lukas Herman
1720eee38c Fix unpropagated audio sampling rate from microphones 2020-10-29 22:41:10 -07:00
Lukas Herman
00877c74a0 Add audio latency detection 2020-10-29 22:37:29 -07:00
Lukas Herman
559c6a13a1 Update readers to be memory pool friendly 2020-10-29 00:04:12 -07:00
Lukas Herman
f4a4edcabd Update codec.Reader interface to return byte slice 2020-10-29 04:48:47 +00:00
Lukas Herman
c8547c4597 Rename simple -> webrtc 2020-10-27 21:13:07 -07:00
Lukas Herman
21bb12dd6b Reduce examples to increase maintainability
Changes:
  * Remove facedetection, rtp-send, and screenshare examples
  * Rename simple to webrtc
2020-10-27 21:10:50 -07:00
Lukas Herman
fd43659fed Remove code owners 2020-10-27 21:09:19 -07:00
Lukas Herman
82f33cb572 Skip mmal package in CI 2020-10-27 21:08:12 -07:00
Lukas Herman
4f9822349a Add mmal hardware video encoder support
Using mmal significantly boosts video fps by offloading the encoding
work from CPU to GPU. On a Raspberry Pi 3, libx264 only gives 480p18,
whereas mmal can give 720p30.
2020-10-27 21:08:12 -07:00
Lukas Herman
16bcd0b7dd Fix step wise resolutions in linux camera
Some cameras support a range of resolutions with step wise. The fix is
to not only capture the highest resolutions but uses the step wise to
determine if we can support the hardcoded standard resolutions,
https://commons.wikimedia.org/wiki/File:Vector_Video_Standards2.svg.
In the future, we should use a custom data structure to capture more
resolutions that are outside of the listed standard resolutions.
2020-10-26 21:26:03 -07:00
Lukas Herman
2022a4b7f7 Fix undiscovered some camera devices
/dev/v4l/by-path doesn't return all available devices. So, to make sure
that we include all available devices, the list of devices will also
complement with /dev/video*.
2020-10-26 20:22:40 -07:00
Renovate Bot
0b6549eb8f Update actions/setup-go action to v2
Generated by Renovate Bot
2020-10-27 10:26:29 +09:00
Renovate Bot
1b0a237438 Update github.com/jfreymuth/pulse commit hash to 1e525c4
Generated by Renovate Bot
2020-10-20 12:40:12 +09:00
Lukas Herman
36edbd9485 Add meta reader helper
Since broadcaster has a ring buffer, we can take advantage of this
property to read the meta data for video/audio without losing any data.
2020-10-15 22:28:37 -04:00
Lukas Herman
eb689a3c79 Remove buffer in linux camera 2020-10-15 00:12:55 -04:00
Lukas Herman
e4b1b1aaba Fix included unsupported formats 2020-10-14 12:18:17 -04:00
Lukas Herman
0f5df05c16 Update lherman-cs/opus to remove libopusfile 2020-10-12 01:32:22 -04:00
Lukas Herman
9dcfaf1c1e Switch from type alias to embedded struct
Embedded struct provides more future compatibility
2020-10-11 23:32:58 -04:00
Lukas Herman
238f190e71 Use MediaDeviceInfo instead of webrtc.RTPCodecType
Changes:
  * Add unit tests for mediastream
  * Remove webrtc.RTPCodecType dependency in mediastream
  * Add Kind to Tracker interface
2020-10-11 01:29:59 -04:00
Lukas Herman
0210ec6ca6 Add audio change detection 2020-10-10 23:47:35 -04:00
Lukas Herman
abdd96e6b2 Replace Name with RTPCodec in codec builder
Allowing users to implement RTPCodec will give users freedom to have
a custom encoder with custom RTP payload.
2020-10-08 11:33:38 -04:00
Lukas Herman
c9779e7f73 Add audio pull-based broadcast 2020-10-05 22:30:03 -04:00
Lukas Herman
5703fd7e4b Remove webrtc dependency in codec and its sub packages 2020-10-05 22:23:52 -04:00
Lukas Herman
db5d8f23bd Fix unstored audio
* Add unit test to check internal buffer integrity
* Fix unstored audio in internal buffer
2020-10-05 22:20:12 -04:00
Lukas Herman
d6ba28af8c Update README.md 2020-10-02 15:56:21 -04:00
Lukas Herman
09c2998408 Add code coverage report 2020-10-02 01:42:01 -04:00
Lukas Herman
d129e982c7 Add generic wave's Buffer 2020-10-02 01:35:41 -04:00
Lukas Herman
74986c010b Move current broadcast test to io
* Move core broadcast tests to io
* Add targeted tests for video.Broadcast
2020-10-02 01:33:21 -04:00
Renovate Bot
b8be865ff3 Update golang.org/x/sys commit hash to fdedc70
Generated by Renovate Bot
2020-10-02 01:13:49 -04:00
Lukas Herman
7aad89ef37 Fix example go module versioning 2020-10-01 21:06:02 -07:00
Renovate Bot
943906e125 Update golang.org/x/image commit hash to e162460
Generated by Renovate Bot
2020-10-01 19:17:59 -04:00
Tarrence van As
f3e3dc9589 use nolibopus in ci 2020-09-29 13:03:21 -04:00
Renovate Bot
a3d374f528 Update github.com/lherman-cs/opus commit hash to 26ea9d3
Generated by Renovate Bot
2020-09-29 13:03:21 -04:00
Lukas Herman
cba0042f5d Fix unalligned panic in 32 bits systems 2020-09-28 20:45:52 -04:00
Atsushi Watanabe
1732e2751d Drop source frames during pause
Source reader should drop frames to catch up the latest frame.
2020-09-28 20:45:52 -04:00
Atsushi Watanabe
5b1527d455 Add broadcast test conditions with pause
Add test case to pause provider feeding or consumer reading
during broadcasting.
2020-09-28 20:45:52 -04:00
Lukas Herman
00f0a44ab1 Add pull-based Broadcaster
* Add generic io.Reader
* Add generic broadcaster
* Add specialize video broadcaster
* Use ring buffer in broadcaster
* Use small delay to relax the schedule in polling
2020-09-28 20:45:52 -04:00
Renovate Bot
a44240be5f Update module pion/webrtc/v2 to v2.2.26
Generated by Renovate Bot
2020-09-21 13:00:01 -07:00
Lukas Herman
70f7360b92 Enhance failed to find driver error message 2020-09-11 12:39:48 -04:00
Lukas Herman
30d49e1fd3 Add human friendly string implementation 2020-09-11 12:39:48 -04:00
Lukas Herman
0cd870fd4b Add generic FrameBuffer 2020-09-07 00:33:25 -04:00
Lukas Herman
13e6dcc437 Remove redundant comments
From pkg.go.dev or godoc, the removed comments are not necessary
as they won't get rendered or goes without saying.
2020-09-06 23:59:28 -04:00
Lukas Herman
366885e01c Hide DecoderFunc
Since DecoderFunc is not being used as a public API, there's no need
to increase the API surface area.
2020-09-06 23:59:28 -04:00
Lukas Herman
86e3a3f14c Update CI to use Go 1.15 and 1.14 2020-09-03 00:12:25 -04:00
Renovate Bot
b4c11d5a0c Update golang.org/x/sys commit hash to 196b9ba
Generated by Renovate Bot
2020-08-31 21:04:03 -04:00
Renovate Bot
18da7ff1c6 Update module pion/webrtc/v2 to v2.2.24
Generated by Renovate Bot
2020-08-23 18:25:51 -07:00
Lukas Herman
f7068296d3 Add V4L2_PIX_FMT_YUV420 support for Linux 2020-08-19 23:09:29 -07:00
Renovate Bot
6d07cc2a58 Update github.com/jfreymuth/pulse commit hash to a82ccdb
Generated by Renovate Bot
2020-08-18 11:41:34 +09:00
Renovate Bot
d857d04dc9 Update github.com/jfreymuth/pulse commit hash to 7d61c49
Generated by Renovate Bot
2020-08-11 01:04:00 +09:00
Renovate Bot
cfdb2221a4 Update module pion/webrtc/v2 to v2.2.23
Generated by Renovate Bot
2020-08-03 10:56:26 -07:00
Renovate Bot
297b4adb4b Update golang.org/x/image commit hash to 972c09e
Generated by Renovate Bot
2020-08-02 17:39:23 +09:00
Renovate Bot
6269ed6508 Update golang.org/x/sys commit hash to 3e129f6
Generated by Renovate Bot
2020-08-02 17:33:04 +09:00
Renovate Bot
aacb05c421 Update module pion/webrtc/v2 to v2.2.22
Generated by Renovate Bot
2020-07-27 16:56:39 +09:00
Renovate Bot
4692cd76e9 Update module pion/webrtc/v2 to v2.2.21
Generated by Renovate Bot
2020-07-25 10:14:43 +09:00
Lukas Herman
2f437a5cc6 Skip time related tests for Darwin 2020-07-13 22:59:13 -04:00
Lukas Herman
fa82237095 Add property change detection for video pipeline 2020-07-06 07:07:41 -04:00
Renovate Bot
74f1fa4910 Update golang.org/x/sys commit hash to ddb9806
Generated by Renovate Bot
2020-07-01 08:17:34 -04:00
Renovate Bot
714d0fa839 Update golang.org/x/image commit hash to c137617
Generated by Renovate Bot
2020-06-30 21:08:52 -04:00
Renovate Bot
6d3f9dbc3e Update module pion/webrtc/v2 to v2.2.17
Generated by Renovate Bot
2020-06-29 11:26:41 -04:00
Lukas Herman
45056e6922 Add IsFloat, IsBigEndian, and IsInterleaved props
* Add bool constraint
* Add IsFloat, IsBigEndian, and IsInterleaved properties
2020-06-22 07:40:05 -04:00
Renovate Bot
a4faa89c6c Update module pion/webrtc/v2 to v2.2.16
Generated by Renovate Bot
2020-06-21 20:56:11 -04:00
Lukas Herman
122aec0536 Make raw audio decoder more practical 2020-06-17 11:02:47 -04:00
Renovate Bot
c3c1177455 Update github.com/jfreymuth/pulse commit hash to 84b2d75
Generated by Renovate Bot
2020-06-14 23:18:00 -04:00
Renovate Bot
74723dd9f1 Update module pion/webrtc/v2 to v2.2.15
Generated by Renovate Bot
2020-06-14 23:13:15 -04:00
Lukas Herman
4fbce4769b Remove unnecessary beep dependency 2020-06-09 09:30:18 -04:00
Atsushi Watanabe
09ff95645e io/audio: fix ChunkInfo of ChannelMixer output 2020-06-09 08:16:34 -04:00
Atsushi Watanabe
1ebba951fb io/audio: fix ChunkInfo of Buffer output 2020-06-09 08:16:34 -04:00
Atsushi Watanabe
cce22b117a prop: compare ChannelCount 2020-06-08 20:43:12 -04:00
Atsushi Watanabe
e87f899777 driver/microphone: use int16 format 2020-06-08 20:43:12 -04:00
Atsushi Watanabe
0d1e856f7d codec/opus: support int16 interleaved format
Implement audio.Buffer and audio.ChannelMixer.
2020-06-08 20:43:12 -04:00
Atsushi Watanabe
d2d9259f15 wave: define EditableAudio interface 2020-06-08 20:43:12 -04:00
Atsushi Watanabe
0c3bf8af3b wave: add SubAudio method
SubAudio returns part of the original audio sharing the buffer.
2020-06-08 20:43:12 -04:00
Lukas Herman
438ee8a3d0 Add decoder benchmark for host vs non-host endian 2020-06-07 11:27:34 -04:00
Lukas Herman
8c49553179 Fix invalid copy for non interleaved 2020-06-07 11:27:34 -04:00
Atsushi Watanabe
6735d5541e Directly copy memory in audio decoder if endianness matches 2020-06-07 11:27:34 -04:00
Lukas Herman
94b57d40e3 Add raw audio decoder
* Add Int16Interleaved and Int16NonInterleaved formats
* Add Float32Interleaved and Float32NonInterleaved formats
* Add unit tests
2020-06-07 09:54:26 -04:00
Lukas Herman
8d7947b594 Fix invalid constraints merging 2020-06-03 11:36:04 -04:00
Lukas Herman
fad6c3ec4b Add darwin camera support
* Add avfoundation Go and C bindings
* Add darwin camera adapter
* Add darwin camera support to README
2020-06-03 10:18:06 -04:00
Atsushi Watanabe
73812503a3 Decrease test data rate for test stability
Ticker on OSX seems not accurate.
2020-06-03 22:07:18 +09:00
Atsushi Watanabe
96c19f3635 Add darwin CI job
Co-authored-by: Lukas Herman <lherman.cs@gmail.com>
2020-06-03 22:07:18 +09:00
Lukas Herman
ea879e1172 Update LICENSE
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2020-06-02 12:07:05 -04:00
Lukas Herman
f641417d1e Renew LICENSE year 2020-06-02 12:07:05 -04:00
Renovate Bot
8bfce0c818 chore(deps): update golang.org/x/sys commit hash to 0598657
Generated by Renovate Bot
2020-06-01 07:06:33 -04:00
Atsushi Watanabe
00eca231a7 Select by DeviceID using StringConstraint 2020-05-24 11:15:29 -04:00
Atsushi Watanabe
27d966611e prop: add documents 2020-05-24 10:26:16 +09:00
Atsushi Watanabe
ecff5e63a5 prop: support ranged/exact/oneof constraints 2020-05-24 10:26:16 +09:00
Atsushi Watanabe
305b7086e3 Fix bitrate measurement stability
- improve accuracy of bitrate calculation
- reduce test input timing error
2020-05-23 15:17:59 -04:00
Renovate Bot
6471064956 Update module pion/webrtc/v2 to v2.2.14
Generated by Renovate Bot
2020-05-20 15:26:18 +09:00
Lukas Herman
c6e685964f Fix wrong required size 2020-05-15 00:48:54 -04:00
Renovate Bot
65b744f639 Update module pion/webrtc/v2 to v2.2.9
Generated by Renovate Bot
2020-05-11 16:20:41 +09:00
Renovate Bot
a2b74babc4 Update github.com/jfreymuth/pulse commit hash to 1534c4a
Generated by Renovate Bot
2020-05-11 16:19:14 +09:00
242 changed files with 16337 additions and 2707 deletions

View File

@@ -8,17 +8,18 @@ on:
- master - master
jobs: jobs:
build: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
go: [ '1.14', '1.13' ] go: [ '1.18', '1.17' ]
name: Go ${{ matrix.go }} name: Linux Go ${{ matrix.go }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v1 uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Install dependencies - name: Install dependencies
@@ -29,19 +30,44 @@ jobs:
libva-dev \ libva-dev \
libvpx-dev \ libvpx-dev \
libx264-dev libx264-dev
- name: go vet - name: Run Test Suite
run: go vet ./... run: make test
- name: go build - uses: codecov/codecov-action@v3
run: go build ./... if: matrix.go == '1.18'
- name: go build without CGO build-darwin:
run: go build . pkg/... runs-on: macos-latest
env: strategy:
CGO_ENABLED: 0 fail-fast: false
- name: go test matrix:
run: go test ./... -v -race go: [ '1.18', '1.17' ]
- name: go test without CGO name: Darwin Go ${{ matrix.go }}
run: go test . pkg/... -v steps:
env: - name: Checkout
CGO_ENABLED: 0 uses: actions/checkout@v3
#- name: golint - name: Setup Go
# run: go lint ./... uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
run: |
brew install \
pkg-config \
opus \
libvpx \
x264
- name: Run Test Suite
run: make test
check-licenses:
runs-on: ubuntu-latest
name: Check Licenses
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.17'
- name: Installing go-licenses
run: go get github.com/google/go-licenses
- name: Checking licenses
run: go-licenses check ./...

View File

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

5
.gitignore vendored
View File

@@ -10,3 +10,8 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
scripts/cross
coverage.txt
.idea

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "cvendor/src/openh264"]
path = cvendor/src/openh264
url = https://github.com/cisco/openh264.git

View File

@@ -1 +0,0 @@
* @lherman-cs @at-wat

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Pion Copyright (c) 2019-2020 Pion
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

127
Makefile
View File

@@ -1,56 +1,83 @@
vendor_dir = cvendor docker_owner := lherman
src_dir = $(vendor_dir)/src docker_prefix := cross
lib_dir = $(vendor_dir)/lib toolchain_dockerfiles := dockerfiles
include_dir = $(vendor_dir)/include script_path := $(realpath scripts)
toolchain_path := $(script_path)/$(docker_prefix)
os_list := \
linux \
windows \
darwin
arch_list := \
armv7 \
arm64 \
x64
supported_platforms := \
linux-armv7 \
linux-arm64 \
linux-x64 \
windows-x64 \
darwin-x64 \
darwin-arm64
cmd_build := build
cmd_test := test
examples_dir := examples
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_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
make_args.x86_64-windows = \ define BUILD_TEMPLATE
CC=x86_64-w64-mingw32-gcc \ ifneq (,$$(findstring $(2)-$(3),$$(supported_platforms)))
CXX=x86_64-w64-mingw32-g++ \ $$(cmd_build)-$(1)-$(2)-$(3): toolchain-$(2)-$(3)
ARCH=x86_64 \ $$(MAKE) --directory=$$(codec_dir)/$(1) \
OS=mingw_nt MEDIADEVICES_TOOLCHAIN_BIN=$$(toolchain_path)/$(docker_prefix)-$(2)-$(3) \
make_args.x86_64-darwin = \ MEDIADEVICES_TARGET_PLATFORM=$(2)-$(3) \
CC=o64-clang \ MEDIADEVICES_TARGET_OS=$(2) \
CXX=o64-clang++ \ MEDIADEVICES_TARGET_ARCH=$(3)
AR=llvm-ar \ endif
ARCH=x86_64 \ endef
OS=darwin
.PHONY: vendor .PHONY: all
vendor: \ all: $(cmd_test) $(cmd_build)
$(include_dir)/openh264 \
cross-libraries
$(include_dir)/openh264: $(src_dir)/openh264 # Subcommand:
mkdir -p $@ # make build[-<codec_name>-<os>-<arch>]
cp $^/codec/api/svc/*.h $@ #
# Description:
# Build codec dependencies to multiple platforms.
#
# Examples:
# * make build: build all codecs for all supported platforms
# * make build-opus-darwin-x64: only build opus for darwin-x64 platform
$(cmd_build): $(targets)
$(lib_dir)/openh264/libopenh264.x86_64-linux.a: $(src_dir)/openh264 toolchain-%: $(toolchain_dockerfiles)
$(MAKE) -C $^ clean \ $(MAKE) --directory=$< "$*" \
&& $(MAKE) -C $^ libraries MEDIADEVICES_DOCKER_OWNER=$(docker_owner) \
mkdir -p $(dir $@) MEDIADEVICES_DOCKER_PREFIX=$(docker_prefix)
cp $^/libopenh264.a $@ @mkdir -p $(toolchain_path)
@docker run $(docker_owner)/$(docker_prefix)-$* > \
$(toolchain_path)/$(docker_prefix)-$*
@chmod +x $(toolchain_path)/$(docker_prefix)-$*
$(lib_dir)/openh264/libopenh264.x86_64-windows.a: $(src_dir)/openh264 $(foreach codec, $(codec_list), \
$(MAKE) -C $^ clean \ $(foreach os, $(os_list), \
&& $(MAKE) -C $^ $(make_args.x86_64-windows) libraries $(foreach arch, $(arch_list), \
mkdir -p $(dir $@) $(eval $(call BUILD_TEMPLATE,$(codec),$(os),$(arch))))))
cp $^/libopenh264.a $@
$(lib_dir)/openh264/libopenh264.x86_64-darwin.a: $(src_dir)/openh264 # Subcommand:
$(MAKE) -C $^ clean \ # make test
&& $(MAKE) -C $^ $(make_args.x86_64-darwin) libraries #
mkdir -p $(dir $@) # Description:
cp $^/libopenh264.a $@ # Run a series of tests
$(cmd_test):
.PHONY: cross-libraries go vet $(pkgs_without_mmal)
cross-libraries: go build $(pkgs_without_mmal)
docker build -t mediadevices-libs-builder -f libs-builder.Dockerfile . # go build without CGO
docker run --rm \ CGO_ENABLED=0 go build $(pkgs_without_cgo)
-v $(CURDIR):/go/src/github.com/pion/mediadevices \ # go build with CGO
mediadevices-libs-builder make $(lib_dir)/openh264/libopenh264.x86_64-linux.a CGO_ENABLED=1 go build $(pkgs_without_mmal)
docker run --rm \ $(MAKE) --directory=$(examples_dir)
-v $(CURDIR):/go/src/github.com/pion/mediadevices \ go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_mmal)
mediadevices-libs-builder make $(lib_dir)/openh264/libopenh264.x86_64-windows.a
docker run --rm \
-v $(CURDIR):/go/src/github.com/pion/mediadevices \
mediadevices-libs-builder make $(lib_dir)/openh264/libopenh264.x86_64-darwin.a

290
README.md
View File

@@ -1,75 +1,237 @@
# mediadevices <h1 align="center">
<br>
Pion MediaDevices
<br>
</h1>
<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>
<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>
<br>
Go implementation of the [MediaDevices](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices) API. `mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
![](img/demo.gif) ## Install
## Interfaces `go get -u github.com/pion/mediadevices`
| Interface | Linux | Mac | Windows |
| :--------: | :---: | :-: | :-----: |
| Camera | ✔️ | ✖️ | ✔️ |
| Microphone | ✔️ | ✖️ | ✔️ |
| Screen | ✔️ | ✖️ | ✖️ |
### Camera
| OS | Library/Interface |
| :-----: | :---------------------------------------------------------------------: |
| Linux | [Video4Linux](https://en.wikipedia.org/wiki/Video4Linux) |
| Mac | N/A |
| Windows | [DirectShow](https://docs.microsoft.com/en-us/windows/win32/directshow) |
| Pixel Format | Linux | Mac | Windows |
| :---------------------------------------------------: | :---: | :-: | :-----: |
| [YUY2](https://www.fourcc.org/pixel-format/yuv-yuy2/) | ✔️ | ✖️ | ✔️ |
| [UYVY](https://www.fourcc.org/pixel-format/yuv-uyvy/) | ✔️ | ✖️ | ✖️ |
| [I420](https://www.fourcc.org/pixel-format/yuv-i420/) | ✔️ | ✖️ | ✖️ |
| [NV21](https://www.fourcc.org/pixel-format/yuv-nv21/) | ✔️ | ✖️ | ✖️ |
| [MJPEG](https://www.fourcc.org/mjpg/) | ✔️ | ✖️ | ✖️ |
### Microphone
| OS | Library/Interface |
| :-----: | :---------------------------------------------------------------------: |
| Linux | [PulseAudio](https://en.wikipedia.org/wiki/PulseAudio) |
| Mac | N/A |
| Windows | [waveIn](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/) |
### Screen casting
| OS | Library/Interface |
| :-----: | :---------------------------------------------------------------------: |
| Linux | [X11](https://en.wikipedia.org/wiki/X_Window_System) |
| Mac | N/A |
| Windows | N/A |
## Codecs
| Audio Codec | Library/Interface |
| :---------: | :------------------------------------------------------: |
| OPUS | [libopus](http://opus-codec.org/) |
| Video Codec | Library/Interface |
| :---------: | :------------------------------------------------------: |
| H.264 | [OpenH264](https://www.openh264.org/) |
| VP8 | [libvpx](https://www.webmproject.org/code/) |
| VP9 | [libvpx](https://www.webmproject.org/code/) |
## Usage ## Usage
[Wiki](https://github.com/pion/mediadevices/wiki) The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
```go
package main
import (
"image/jpeg"
"os"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/prop"
// This is required to register camera adapter
_ "github.com/pion/mediadevices/pkg/driver/camera"
// Note: If you don't have a camera or your adapters are not supported,
// you can always swap your adapters with our dummy adapters below.
// _ "github.com/pion/mediadevices/pkg/driver/videotest"
)
func main() {
stream, _ := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(constraint *mediadevices.MediaTrackConstraints) {
// Query for ideal resolutions
constraint.Width = prop.Int(600)
constraint.Height = prop.Int(400)
},
})
// Since track can represent audio as well, we need to cast it to
// *mediadevices.VideoTrack to get video specific functionalities
track := stream.GetVideoTracks()[0]
videoTrack := track.(*mediadevices.VideoTrack)
defer videoTrack.Close()
// Create a new video reader to get the decoded frames. Release is used
// to return the buffer to hold frame back to the source so that the buffer
// can be reused for the next frames.
videoReader := videoTrack.NewReader(false)
frame, release, _ := videoReader.Read()
defer release()
// Since frame is the standard image.Image, it's compatible with Go standard
// library. For example, capturing the first frame and store it as a jpeg image.
output, _ := os.Create("frame.jpg")
jpeg.Encode(output, frame, nil)
}
```
## 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 |
| :--------: | :---: | :-: | :-----: |
| Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | ✔️ | ✔️ |
By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
```go
import (
...
_ "github.com/pion/mediadevices/pkg/driver/camera"
)
```
## 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
package main
import (
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec/x264" // This is required to use H264 video encoder
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
)
func main() {
// configure codec specific parameters
x264Params, _ := x264.NewParams()
x264Params.Preset = x264.PresetMedium
x264Params.BitRate = 1_000_000 // 1mbps
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
)
mediaStream, _ := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {},
Codec: codecSelector, // let GetUsermedia know available codecs
})
}
```
Since `mediadevices` doesn't implement the video/audio codecs, it needs to call the codec libraries from the system through cgo. Therefore, you're required to install the codec libraries before you can use them in `mediadevices`. In the next section, it shows a list of available codecs, where the packages are defined (documentation linked), and installation instructions.
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
### Video Codecs
#### 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)
* Installation:
* Mac: `brew install x264`
* Ubuntu: `apt install libx264-dev`
#### 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
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
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)
* Installation:
* Mac: `brew install libvpx`
* Ubuntu: `apt install libvpx-dev`
#### 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)
* Installation:
* Ubuntu: `apt install libva-dev`
### Audio Codecs
#### 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)
* Installation:
* Mac: `brew install opus`
* Ubuntu: `apt install libopus-dev`
## 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
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
1. Open all registered drivers
2. Get all properties (property describes what a driver is capable of, e.g. resolution, frame rate, etc.) from opened drivers
3. Find the best property that meets the criteria
So, when `mediadevices` returns `failed to find the best driver that fits the constraints` error, one of the following conditions might have occured:
* 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`
### 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:
```
# pkg-config --cflags -- vpx
Package vpx was not found in the pkg-config search path.
Perhaps you should add the directory containing `vpx.pc'
to the PKG_CONFIG_PATH environment variable
No package 'vpx' found
pkg-config: exit status 1
```
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`.
## 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.
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 ## Contributing
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
- [Lukas Herman](https://github.com/lherman-cs) - _Original Author_ * [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._ * [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
## Project Status ## License
MIT License - see [LICENSE](LICENSE) for full text
[![Stargazers over time](https://starchart.cc/pion/mediadevices.svg)](https://starchart.cc/pion/mediadevices)
## References
- https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs
- https://tools.ietf.org/html/rfc7742

141
codec.go Normal file
View File

@@ -0,0 +1,141 @@
package mediadevices
import (
"errors"
"fmt"
"strings"
"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/webrtc/v3"
)
// CodecSelector is a container of video and audio encoder builders, which later will be used
// for codec matching.
type CodecSelector struct {
videoEncoders []codec.VideoEncoderBuilder
audioEncoders []codec.AudioEncoderBuilder
}
// CodecSelectorOption is a type for specifying CodecSelector options
type CodecSelectorOption func(*CodecSelector)
// WithVideoEncoders replace current video codecs with listed encoders
func WithVideoEncoders(encoders ...codec.VideoEncoderBuilder) CodecSelectorOption {
return func(t *CodecSelector) {
t.videoEncoders = encoders
}
}
// WithVideoEncoders replace current audio codecs with listed encoders
func WithAudioEncoders(encoders ...codec.AudioEncoderBuilder) CodecSelectorOption {
return func(t *CodecSelector) {
t.audioEncoders = encoders
}
}
// NewCodecSelector constructs CodecSelector with given variadic options
func NewCodecSelector(opts ...CodecSelectorOption) *CodecSelector {
var track CodecSelector
for _, opt := range opts {
opt(&track)
}
return &track
}
// Populate lets the webrtc engine be aware of supported codecs that are contained in CodecSelector
func (selector *CodecSelector) Populate(setting *webrtc.MediaEngine) {
for _, encoder := range selector.videoEncoders {
setting.RegisterCodec(encoder.RTPCodec().RTPCodecParameters, webrtc.RTPCodecTypeVideo)
}
for _, encoder := range selector.audioEncoders {
setting.RegisterCodec(encoder.RTPCodec().RTPCodecParameters, webrtc.RTPCodecTypeAudio)
}
}
// selectVideoCodecByNames selects a single codec that can be built and matched. codecNames can be formatted as "video/<codecName>" or "<codecName>"
func (selector *CodecSelector) selectVideoCodecByNames(reader video.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
var selectedEncoder codec.VideoEncoderBuilder
var encodedReader codec.ReadCloser
var errReasons []string
var err error
outer:
for _, wantCodec := range codecNames {
wantCodecLower := strings.ToLower(wantCodec)
for _, encoder := range selector.videoEncoders {
// MimeType is formated as "video/<codecName>"
if strings.HasSuffix(strings.ToLower(encoder.RTPCodec().MimeType), wantCodecLower) {
encodedReader, err = encoder.BuildVideoEncoder(reader, inputProp)
if err == nil {
selectedEncoder = encoder
break outer
}
}
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().MimeType, err))
}
}
if selectedEncoder == nil {
return nil, nil, errors.New(strings.Join(errReasons, "\n\n"))
}
return encodedReader, selectedEncoder.RTPCodec(), nil
}
func (selector *CodecSelector) selectVideoCodec(reader video.Reader, inputProp prop.Media, codecs ...webrtc.RTPCodecParameters) (codec.ReadCloser, *codec.RTPCodec, error) {
var codecNames []string
for _, codec := range codecs {
codecNames = append(codecNames, codec.MimeType)
}
return selector.selectVideoCodecByNames(reader, inputProp, codecNames...)
}
// selectAudioCodecByNames selects a single codec that can be built and matched. codecNames can be formatted as "audio/<codecName>" or "<codecName>"
func (selector *CodecSelector) selectAudioCodecByNames(reader audio.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
var selectedEncoder codec.AudioEncoderBuilder
var encodedReader codec.ReadCloser
var errReasons []string
var err error
outer:
for _, wantCodec := range codecNames {
wantCodecLower := strings.ToLower(wantCodec)
for _, encoder := range selector.audioEncoders {
// MimeType is formated as "audio/<codecName>"
if strings.HasSuffix(strings.ToLower(encoder.RTPCodec().MimeType), wantCodecLower) {
encodedReader, err = encoder.BuildAudioEncoder(reader, inputProp)
if err == nil {
selectedEncoder = encoder
break outer
}
}
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().MimeType, err))
}
}
if selectedEncoder == nil {
return nil, nil, errors.New(strings.Join(errReasons, "\n\n"))
}
return encodedReader, selectedEncoder.RTPCodec(), nil
}
func (selector *CodecSelector) selectAudioCodec(reader audio.Reader, inputProp prop.Media, codecs ...webrtc.RTPCodecParameters) (codec.ReadCloser, *codec.RTPCodec, error) {
var codecNames []string
for _, codec := range codecs {
codecNames = append(codecNames, codec.MimeType)
}
return selector.selectAudioCodecByNames(reader, inputProp, codecNames...)
}

10
codecov.yml Normal file
View File

@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
# Allow decreasing 2% of total coverage to avoid noise.
threshold: 2%
patch: off
ignore:
- "examples/*"

View File

@@ -1,15 +0,0 @@
//The current file is auto-generated by script: generate_codec_ver.sh
#ifndef CODEC_VER_H
#define CODEC_VER_H
#include "codec_app_def.h"
static const OpenH264Version g_stCodecVersion = {2, 0, 0, 1905};
static const char* const g_strCodecVer = "OpenH264 version:2.0.0.1905";
#define OPENH264_MAJOR (2)
#define OPENH264_MINOR (0)
#define OPENH264_REVISION (0)
#define OPENH264_RESERVED (1905)
#endif // CODEC_VER_H

11
dockerfiles/Makefile Normal file
View File

@@ -0,0 +1,11 @@
dockerfiles := $(wildcard *.Dockerfile)
supported_platforms := $(dockerfiles:.Dockerfile=)
.PHONY: all
all: $(supported_platforms)
%: %.Dockerfile guard-MEDIADEVICES_DOCKER_OWNER guard-MEDIADEVICES_DOCKER_PREFIX
docker build -t "$(MEDIADEVICES_DOCKER_OWNER)/$(MEDIADEVICES_DOCKER_PREFIX)-$@" -f "$<" .
guard-%:
@if [ -z ${$*} ]; then echo "$* is a required environment variable"; exit 1; fi

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
FROM dockcross/base
ENV OSX_CROSS_PATH=/osxcross
COPY --from=dockercore/golang-cross "${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=x86_64-apple-darwin14-clang \
CXX=x86_64-apple-darwin14-clang++ \
CPP=x86_64-apple-darwin14-clang++ \
AR=x86_64-apple-darwin14-ar \
AS=x86_64-apple-darwin14-as \
LD=x86_64-apple-darwin14-ld
COPY darwin-x64.cmake ${OSX_CROSS_PATH}/
ENV CMAKE_TOOLCHAIN_FILE ${OSX_CROSS_PATH}/darwin-x64.cmake
ARG IMAGE=lherman/cross-darwin-x64
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 x86_64)
set(CMAKE_C_COMPILER $ENV{CC})
set(CMAKE_CXX_COMPILER $ENV{CXX})
set(CMAKE_AR $ENV{AR})
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})

7
dockerfiles/init.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
apt-get update
apt-get install -y nasm clang llvm
curl -L https://golang.org/dl/go1.15.6.linux-amd64.tar.gz | tar -C /usr/local -xzf -
ln -s /usr/local/go/bin/go /usr/local/bin/go

View File

@@ -0,0 +1,8 @@
FROM dockcross/linux-arm64
ARG IMAGE=lherman/cross-linux-arm64
ARG VERSION=latest
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}
COPY init.sh /tmp/init.sh
RUN bash /tmp/init.sh

View File

@@ -0,0 +1,8 @@
FROM dockcross/linux-armv7
ARG IMAGE=lherman/cross-linux-armv7
ARG VERSION=latest
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}
COPY init.sh /tmp/init.sh
RUN bash /tmp/init.sh

View File

@@ -0,0 +1,8 @@
FROM dockcross/linux-x64
ARG IMAGE=lherman/cross-linux-x64
ARG VERSION=latest
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}
COPY init.sh /tmp/init.sh
RUN bash /tmp/init.sh

View File

@@ -0,0 +1,11 @@
FROM dockcross/windows-static-x64-posix
ARG IMAGE=lherman/cross-windows-x64
ARG VERSION=latest
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}
COPY init.sh /tmp/init.sh
RUN bash /tmp/init.sh
RUN ln -s /usr/src/mxe/usr/bin/x86_64-w64-mingw32.static.posix-gcc /usr/bin/x86_64-w64-mingw32-gcc && \
ln -s /usr/src/mxe/usr/bin/x86_64-w64-mingw32.static.posix-g++ /usr/bin/x86_64-w64-mingw32-g++ && \
ln -s /usr/src/mxe/usr/bin/x86_64-w64-mingw32.static.posix-ar /usr/bin/x86_64-w64-mingw32-ar

1
examples/.gitignore vendored
View File

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

8
examples/Makefile Normal file
View File

@@ -0,0 +1,8 @@
examples := $(shell find * -maxdepth 0 -type d)
examples := $(filter-out internal,$(examples))
.PHONY: all $(examples)
all: $(examples)
$(examples):
cd $@ && go build -mod=mod

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

@@ -0,0 +1 @@
archive

View File

@@ -0,0 +1,38 @@
## Instructions
### Install required codecs
In this example, we'll be using x264 as our video codec. Therefore, we need to make sure that these codecs are installed within our system.
Installation steps:
* [x264](https://github.com/pion/mediadevices#x264)
### Download archive examplee
```
git clone https://github.com/pion/mediadevices.git
```
### Run archive example
Run `cd mediadevices/examples/archive && go build && ./archive recorded.h264`
To stop recording, press `Ctrl+c` or send a SIGINT signal.
### Playback recorded video
Install GStreamer and run:
```
gst-launch-1.0 playbin uri=file://${PWD}/recorded.h264
```
Or run VLC media plyer:
```
vlc recorded.h264
```
A video should start playing in your GStreamer or VLC window.
Congrats, you have used pion-MediaDevices! Now start building something cool

82
examples/archive/main.go Normal file
View File

@@ -0,0 +1,82 @@
package main
import (
"fmt"
"image"
"io"
"os"
"os/signal"
"syscall"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec/x264" // This is required to use H264 video encoder
_ "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/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
func must(err error) {
if err != nil {
panic(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s <path/to/file.h264>\n", os.Args[0])
return
}
dest := os.Args[1]
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT)
x264Params, err := x264.NewParams()
must(err)
x264Params.Preset = x264.PresetMedium
x264Params.BitRate = 1_000_000 // 1mbps
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
)
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
c.Width = prop.Int(640)
c.Height = prop.Int(480)
},
Codec: codecSelector,
})
must(err)
videoTrack := mediaStream.GetVideoTracks()[0].(*mediadevices.VideoTrack)
defer videoTrack.Close()
videoTrack.Transform(video.TransformFunc(func(r video.Reader) video.Reader {
return video.ReaderFunc(func() (img image.Image, release func(), err error) {
// we send io.EOF signal to the encoder reader to stop reading. Therefore, io.Copy
// will finish its execution and the program will finish
select {
case <-sigs:
return nil, func() {}, io.EOF
default:
}
return r.Read()
})
}))
reader, err := videoTrack.NewEncodedIOReader(x264Params.RTPCodec().MimeType)
must(err)
defer reader.Close()
out, err := os.Create(dest)
must(err)
fmt.Println("Recording... Press Ctrl+c to stop")
_, err = io.Copy(out, reader)
must(err)
fmt.Println("Your video has been recorded to", dest)
}

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

@@ -0,0 +1 @@
facedetection

View File

@@ -1,29 +1,15 @@
## Instructions ## Instructions
### Download facedetection ### Download facedetection example
``` ```
go get github.com/pion/mediadevices/examples/facedetection git clone https://github.com/pion/mediadevices.git
``` ```
### Open example page ### Compile and Run facedetection
[jsfiddle.net](https://jsfiddle.net/gh/get/library/pure/pion/mediadevices/tree/master/examples/internal/jsfiddle/video) you should see two text-areas and a 'Start Session' button Run `cd mediadevices/examples/facedetection && go build && ./facedetection`
### Run facedetection with your browsers SessionDescription as stdin You should be able to see some loggings when it can see faces.
In the jsfiddle the top textarea is your browser, copy that and: Congrats, you have used pion-MediaDevices! Now start building something cool
#### Linux
Run `echo $BROWSER_SDP | facedetection`
### Input facedetection's SessionDescription into your browser
Copy the text that `facedetection` 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-WebRTC! Now start building something cool

View File

@@ -1,118 +0,0 @@
package main
import (
"image"
"image/color"
"image/draw"
"io/ioutil"
"log"
"github.com/disintegration/imaging"
pigo "github.com/esimov/pigo/core"
)
var (
cascade []byte
err error
classifier *pigo.Pigo
)
func imgToGrayscale(img image.Image) []uint8 {
bounds := img.Bounds()
flatten := bounds.Dy() * bounds.Dx()
grayImg := make([]uint8, flatten)
i := 0
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
pix := img.At(x, y)
grayPix := color.GrayModel.Convert(pix).(color.Gray)
grayImg[i] = grayPix.Y
i++
}
}
return grayImg
}
// clusterDetection runs Pigo face detector core methods
// and returns a cluster with the detected faces coordinates.
func clusterDetection(img image.Image) []pigo.Detection {
grayscale := imgToGrayscale(img)
bounds := img.Bounds()
cParams := pigo.CascadeParams{
MinSize: 100,
MaxSize: 600,
ShiftFactor: 0.15,
ScaleFactor: 1.1,
ImageParams: pigo.ImageParams{
Pixels: grayscale,
Rows: bounds.Dy(),
Cols: bounds.Dx(),
Dim: bounds.Dx(),
},
}
if len(cascade) == 0 {
cascade, err = ioutil.ReadFile("facefinder")
if err != nil {
log.Fatalf("Error reading the cascade file: %s", err)
}
p := pigo.NewPigo()
// Unpack the binary file. This will return the number of cascade trees,
// the tree depth, the threshold and the prediction from tree's leaf nodes.
classifier, err = p.Unpack(cascade)
if err != nil {
log.Fatalf("Error unpacking the cascade file: %s", err)
}
}
// Run the classifier over the obtained leaf nodes and return the detection results.
// The result contains quadruplets representing the row, column, scale and detection score.
dets := classifier.RunCascade(cParams, 0.0)
// Calculate the intersection over union (IoU) of two clusters.
dets = classifier.ClusterDetections(dets, 0)
return dets
}
func drawCircle(img draw.Image, x0, y0, r int, c color.Color) {
x, y, dx, dy := r-1, 0, 1, 1
err := dx - (r * 2)
for x > y {
img.Set(x0+x, y0+y, c)
img.Set(x0+y, y0+x, c)
img.Set(x0-y, y0+x, c)
img.Set(x0-x, y0+y, c)
img.Set(x0-x, y0-y, c)
img.Set(x0-y, y0-x, c)
img.Set(x0+y, y0-x, c)
img.Set(x0+x, y0-y, c)
if err <= 0 {
y++
err += dy
dy += 2
}
if err > 0 {
x--
dx += 2
err += dx - (r * 2)
}
}
}
func markFaces(img image.Image) image.Image {
nrgba := imaging.Clone(img)
dets := clusterDetection(img)
for _, det := range dets {
if det.Q < 5.0 {
continue
}
drawCircle(nrgba, det.Col, det.Row, det.Scale/2, color.Black)
}
return nrgba
}

View File

@@ -1,118 +1,107 @@
package main package main
import ( import (
"fmt"
"image" "image"
"io/ioutil"
"log"
"time"
pigo "github.com/esimov/pigo/core"
"github.com/pion/mediadevices" "github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video" _ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/webrtc/v2" "github.com/pion/mediadevices/pkg/prop"
) )
func markFacesTransformer(r video.Reader) video.Reader { const (
return video.ReaderFunc(func() (img image.Image, err error) { confidenceLevel = 5.0
img, err = r.Read() )
var (
cascade []byte
classifier *pigo.Pigo
)
func must(err error) {
if err != nil { if err != nil {
return panic(err)
}
} }
img = markFaces(img) func detectFace(frame *image.YCbCr) bool {
return bounds := frame.Bounds()
}) cascadeParams := pigo.CascadeParams{
MinSize: 100,
MaxSize: 600,
ShiftFactor: 0.15,
ScaleFactor: 1.1,
ImageParams: pigo.ImageParams{
Pixels: frame.Y, // Y in YCbCr should be enough to detect faces
Rows: bounds.Dy(),
Cols: bounds.Dx(),
Dim: bounds.Dx(),
},
}
// Run the classifier over the obtained leaf nodes and return the detection results.
// The result contains quadruplets representing the row, column, scale and detection score.
dets := classifier.RunCascade(cascadeParams, 0.0)
// Calculate the intersection over union (IoU) of two clusters.
dets = classifier.ClusterDetections(dets, 0)
for _, det := range dets {
if det.Q >= confidenceLevel {
return true
}
}
return false
} }
func main() { func main() {
config := webrtc.Configuration{ // prepare face detector
ICEServers: []webrtc.ICEServer{ var err error
{ cascade, err = ioutil.ReadFile("facefinder")
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{}
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
panic(err)
}
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
peerConnection, err := api.NewPeerConnection(config)
if err != nil { if err != nil {
panic(err) log.Fatalf("Error reading the cascade file: %s", err)
} }
p := pigo.NewPigo()
// Set the handler for ICE connection state // Unpack the binary file. This will return the number of cascade trees,
// This will notify you when the peer has connected/disconnected // the tree depth, the threshold and the prediction from tree's leaf nodes.
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { classifier, err = p.Unpack(cascade)
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
md := mediadevices.NewMediaDevices(peerConnection)
vp8Params, err := vpx.NewVP8Params()
if err != nil { if err != nil {
panic(err) log.Fatalf("Error unpacking the cascade file: %s", err)
} }
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{ mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) { Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatI420 // most of the encoder accepts I420 c.FrameFormat = prop.FrameFormatOneOf{frame.FormatI420, frame.FormatYUY2}
c.Enabled = true c.Width = prop.Int(640)
c.Width = 640 c.Height = prop.Int(480)
c.Height = 480
c.VideoTransform = markFacesTransformer
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
}, },
}) })
if err != nil { must(err)
panic(err)
// since we're trying to access the raw data, we need to cast Track to its real type, *mediadevices.VideoTrack
videoTrack := mediaStream.GetVideoTracks()[0].(*mediadevices.VideoTrack)
defer videoTrack.Close()
videoReader := videoTrack.NewReader(false)
// To save resources, we can simply use 4 fps to detect faces.
ticker := time.NewTicker(time.Millisecond * 250)
defer ticker.Stop()
for range ticker.C {
frame, release, err := videoReader.Read()
must(err)
// 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")
} }
for _, tracker := range s.GetTracks() { release()
t := tracker.Track()
tracker.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n",
t.ID(), t.Label(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(t,
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)
}
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
select {}
}

View File

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

177
examples/go.sum Normal file
View File

@@ -0,0 +1,177 @@
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
github.com/esimov/pigo v1.4.3 h1:xl098Z9CHmouywvyRZepuKx8aSWHBs/0lZtp7Yt5g28=
github.com/esimov/pigo v1.4.3/go.mod h1:aOTYpOWsqniACzXKdSOGkqI6CnWQpP8tFjgtUOARoEs=
github.com/fogleman/gg v1.0.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.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo=
github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/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.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
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.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
github.com/pion/interceptor v0.1.10 h1:DJ2GjMGm4XGIQgMJxuEpdaExdY/6RdngT7Uh4oVmquU=
github.com/pion/interceptor v0.1.10/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
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.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
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/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
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.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pion/webrtc/v3 v3.1.34 h1:GUfv2zxWge77x1FhZ6Fge8KQd3bTzvX1CMN1/LmTazM=
github.com/pion/webrtc/v3 v3.1.34/go.mod h1:jClfnbJzt8wtmewGxhPzgE5wZ0U/gNB78XCtmc/uz3k=
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/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd h1:zYlwaUHTmxuf6H7hwO2dgwqozQmH7zf4x+/qql4oVWc=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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/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-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-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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/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/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/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=
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

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

@@ -0,0 +1 @@
http

19
examples/http/README.md Normal file
View File

@@ -0,0 +1,19 @@
## Instructions
### Download http example
```
git clone https://github.com/pion/mediadevices.git
```
### Compile and Run HTTP server
Run `cd mediadevices/examples/http && go build && ./http :1313`
### Access the camera stream from the browser
Go to "http://localhost:1313"
Congrats, you have used pion-MediaDevices! Now start building something cool

85
examples/http/main.go Normal file
View File

@@ -0,0 +1,85 @@
// This is an example of using mediadevices to broadcast your camera through http.
// The example doesn't aim to be performant, but rather it strives to be simple.
package main
import (
"bytes"
"fmt"
"image/jpeg"
"io"
"log"
"mime/multipart"
"net/http"
"net/textproto"
"os"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/prop"
// 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/camera" // This is required to register camera adapter
)
func must(err error) {
if err != nil {
panic(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s host:port\n", os.Args[0])
return
}
dest := os.Args[1]
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(constraint *mediadevices.MediaTrackConstraints) {
constraint.Width = prop.Int(600)
constraint.Height = prop.Int(400)
},
})
must(err)
track := mediaStream.GetVideoTracks()[0]
videoTrack := track.(*mediadevices.VideoTrack)
defer videoTrack.Close()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var buf bytes.Buffer
videoReader := videoTrack.NewReader(false)
mimeWriter := multipart.NewWriter(w)
contentType := fmt.Sprintf("multipart/x-mixed-replace;boundary=%s", mimeWriter.Boundary())
w.Header().Add("Content-Type", contentType)
partHeader := make(textproto.MIMEHeader)
partHeader.Add("Content-Type", "image/jpeg")
for {
frame, release, err := videoReader.Read()
if err == io.EOF {
return
}
must(err)
err = jpeg.Encode(&buf, frame, nil)
// Since we're done with img, we need to release img so that that the original owner can reuse
// this memory.
release()
must(err)
partWriter, err := mimeWriter.CreatePart(partHeader)
must(err)
_, err = partWriter.Write(buf.Bytes())
buf.Reset()
must(err)
}
})
fmt.Printf("listening on %s\n", dest)
log.Println(http.ListenAndServe(dest, nil))
}

View File

@@ -1,29 +0,0 @@
## Instructions
### Download rtp-send example
```
go get github.com/pion/mediadevices/examples/rtp-send
```
### Listen RTP
Install GStreamer and run:
```
gst-launch-1.0 udpsrc port=5000 caps=application/x-rtp,encode-name=VP8 \
! rtpvp8depay ! vp8dec ! videoconvert ! autovideosink
```
Or run VLC media plyer:
```
vlc ./vp8.sdp
```
### Run rtp-send
Run `rtp-send localhost:5000`
A video should start playing in your GStreamer or VLC window.
It's not WebRTC, but pure RTP.
Congrats, you have used pion-MediaDevices! Now start building something cool

View File

@@ -1,119 +0,0 @@
package main
import (
"fmt"
"net"
"os"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/rtp"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v2/pkg/media"
)
const (
mtu = 1000
)
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s host:port\n", os.Args[0])
return
}
md := mediadevices.NewMediaDevicesFromCodecs(
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
webrtc.RTPCodecTypeVideo: []*webrtc.RTPCodec{
webrtc.NewRTPVP8Codec(100, 90000),
},
},
mediadevices.WithTrackGenerator(
func(_ uint8, _ uint32, id, _ string, codec *webrtc.RTPCodec) (
mediadevices.LocalTrack, error,
) {
return newTrack(codec, id, os.Args[1]), nil
},
),
)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
_, err = md.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatYUY2
c.Enabled = true
c.Width = 640
c.Height = 480
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
})
if err != nil {
panic(err)
}
select {}
}
type track struct {
codec *webrtc.RTPCodec
packetizer rtp.Packetizer
id string
conn net.Conn
}
func newTrack(codec *webrtc.RTPCodec, id, dest string) *track {
addr, err := net.ResolveUDPAddr("udp", dest)
if err != nil {
panic(err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
panic(err)
}
return &track{
codec: codec,
packetizer: rtp.NewPacketizer(
mtu,
codec.PayloadType,
1,
codec.Payloader,
rtp.NewRandomSequencer(),
codec.ClockRate,
),
id: id,
conn: conn,
}
}
func (t *track) WriteSample(s media.Sample) error {
buf := make([]byte, mtu)
pkts := t.packetizer.Packetize(s.Data, s.Samples)
for _, p := range pkts {
n, err := p.MarshalTo(buf)
if err != nil {
panic(err)
}
_, _ = t.conn.Write(buf[:n])
}
return nil
}
func (t *track) Codec() *webrtc.RTPCodec {
return t.codec
}
func (t *track) ID() string {
return t.id
}
func (t *track) Kind() webrtc.RTPCodecType {
return t.codec.Type
}

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

@@ -0,0 +1 @@
rtp

38
examples/rtp/README.md Normal file
View File

@@ -0,0 +1,38 @@
## Instructions
### Install required codecs
In this example, we'll be using x264 as our video codec. Therefore, we need to make sure that these codecs are installed within our system.
Installation steps:
* [x264](https://github.com/pion/mediadevices#x264)
### Download rtp example
```
git clone https://github.com/pion/mediadevices.git
```
### Listen RTP
Install GStreamer and run:
```
gst-launch-1.0 udpsrc port=5000 caps=application/x-rtp,encode-name=H264 \
! rtph264depay ! avdec_h264 ! videoconvert ! autovideosink
```
Or run VLC media plyer:
```
vlc ./h264.sdp
```
### Run rtp
Run `cd mediadevices/examples/archive && go build && ./rtp localhost:5000`
A video should start playing in your GStreamer or VLC window.
It's not WebRTC, but pure RTP.
Congrats, you have used pion-MediaDevices! Now start building something cool

View File

@@ -6,4 +6,4 @@ c=IN IP4 0.0.0.0
t=0 0 t=0 0
a=recvonly a=recvonly
m=video 5000 RTP/AVP 100 m=video 5000 RTP/AVP 100
a=rtpmap:100 VP8/90000 a=rtpmap:100 H264/90000

78
examples/rtp/main.go Normal file
View File

@@ -0,0 +1,78 @@
package main
import (
"fmt"
"math/rand"
"net"
"os"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec/x264" // This is required to use H264 video encoder
_ "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/prop"
)
const (
mtu = 1000
)
func must(err error) {
if err != nil {
panic(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s host:port\n", os.Args[0])
return
}
dest := os.Args[1]
x264Params, err := x264.NewParams()
must(err)
x264Params.Preset = x264.PresetMedium
x264Params.BitRate = 1_000_000 // 1mbps
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
)
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
c.Width = prop.Int(640)
c.Height = prop.Int(480)
},
Codec: codecSelector,
})
must(err)
videoTrack := mediaStream.GetVideoTracks()[0]
defer videoTrack.Close()
rtpReader, err := videoTrack.NewRTPReader(x264Params.RTPCodec().MimeType, rand.Uint32(), mtu)
must(err)
addr, err := net.ResolveUDPAddr("udp", dest)
must(err)
conn, err := net.DialUDP("udp", nil, addr)
must(err)
buff := make([]byte, mtu)
for {
pkts, release, err := rtpReader.Read()
must(err)
for _, pkt := range pkts {
n, err := pkt.MarshalTo(buff)
must(err)
_, err = conn.Write(buff[:n])
must(err)
}
release()
}
}

View File

@@ -1,29 +0,0 @@
## Instructions
### Download screenshare
```
go get github.com/pion/mediadevices/examples/screenshare
```
### 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 screenshare with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser, copy that and:
#### Linux
Run `echo $BROWSER_SDP | screenshare`
### Input screenshare's SessionDescription into your browser
Copy the text that `screenshare` 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-WebRTC! Now start building something cool

View File

@@ -1,101 +0,0 @@
package main
import (
"fmt"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/screen" // This is required to register screen capture adapter
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/webrtc/v2"
)
func main() {
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{}
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
panic(err)
}
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())
})
md := mediadevices.NewMediaDevices(peerConnection)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetDisplayMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.VideoTransform = video.Scale(-1, 360, nil) // Resize to 360p
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
})
if err != nil {
panic(err)
}
for _, tracker := range s.GetTracks() {
t := tracker.Track()
tracker.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n",
t.ID(), t.Label(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(t,
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)
}
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
select {}
}

View File

@@ -1,29 +0,0 @@
## Instructions
### Download gstreamer-send
```
go get github.com/pion/mediadevices/examples/simple
```
### 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 simple with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser, copy that and:
#### Linux
Run `echo $BROWSER_SDP | simple`
### Input simple's SessionDescription into your browser
Copy the text that `simple` 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-WebRTC! Now start building something cool

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 {}
}

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

@@ -0,0 +1 @@
webrtc

42
examples/webrtc/README.md Normal file
View File

@@ -0,0 +1,42 @@
## 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)
* [opus](https://github.com/pion/mediadevices#opus)
### Download webrtc example
```
git clone https://github.com/pion/mediadevices.git
```
#### Compile webrtc example
```
cd mediadevices/examples/webrtc && 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 | ./webrtc`
### 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

View File

@@ -5,18 +5,18 @@ import (
"github.com/pion/mediadevices" "github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal" "github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/webrtc/v2" "github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v3"
// This is required to use opus audio encoder // If you don't like x264, you can also use vpx by importing as below
"github.com/pion/mediadevices/pkg/codec/opus" // "github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
// If you don't like vpx, you can also use x264 by importing as below
// "github.com/pion/mediadevices/pkg/codec/x264" // This is required to use h264 video encoder
// or you can also use openh264 for alternative h264 implementation // or you can also use openh264 for alternative h264 implementation
// "github.com/pion/mediadevices/pkg/codec/openh264" // "github.com/pion/mediadevices/pkg/codec/openh264"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder // 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/opus" // This is required to use opus audio encoder
"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, // 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. // you can always swap your adapters with our dummy adapters below.
@@ -26,10 +26,6 @@ import (
_ "github.com/pion/mediadevices/pkg/driver/microphone" // This is required to register microphone adapter _ "github.com/pion/mediadevices/pkg/driver/microphone" // This is required to register microphone adapter
) )
const (
videoCodecName = webrtc.VP8
)
func main() { func main() {
config := webrtc.Configuration{ config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{ ICEServers: []webrtc.ICEServer{
@@ -44,11 +40,24 @@ func main() {
signal.Decode(signal.MustReadStdin(), &offer) signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection // Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{} x264Params, err := x264.NewParams()
if err := mediaEngine.PopulateFromSDP(offer); err != nil { if err != nil {
panic(err) panic(err)
} }
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine)) x264Params.BitRate = 500_000 // 500kbps
opusParams, err := opus.NewParams()
if err != nil {
panic(err)
}
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
mediadevices.WithAudioEncoders(&opusParams),
)
mediaEngine := webrtc.MediaEngine{}
codecSelector.Populate(&mediaEngine)
api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine))
peerConnection, err := api.NewPeerConnection(config) peerConnection, err := api.NewPeerConnection(config)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -60,44 +69,27 @@ func main() {
fmt.Printf("Connection State has changed %s \n", connectionState.String()) fmt.Printf("Connection State has changed %s \n", connectionState.String())
}) })
md := mediadevices.NewMediaDevices(peerConnection) s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
opusParams, err := opus.NewParams()
if err != nil {
panic(err)
}
opusParams.BitRate = 32000 // 32kbps
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
Audio: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&opusParams}
},
Video: func(c *mediadevices.MediaTrackConstraints) { Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatYUY2 c.FrameFormat = prop.FrameFormat(frame.FormatI420)
c.Enabled = true c.Width = prop.Int(640)
c.Width = 640 c.Height = prop.Int(480)
c.Height = 480
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
}, },
Audio: func(c *mediadevices.MediaTrackConstraints) {
},
Codec: codecSelector,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
} }
for _, tracker := range s.GetTracks() { for _, track := range s.GetTracks() {
t := tracker.Track() track.OnEnded(func(err error) {
tracker.OnEnded(func(err error) { fmt.Printf("Track (ID: %s) ended with error: %v\n",
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n", track.ID(), err)
t.ID(), t.Label(), err)
}) })
_, err = peerConnection.AddTransceiverFromTrack(t,
_, err = peerConnection.AddTransceiverFromTrack(track,
webrtc.RtpTransceiverInit{ webrtc.RtpTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly, Direction: webrtc.RTPTransceiverDirectionSendonly,
}, },
@@ -119,13 +111,23 @@ func main() {
panic(err) panic(err)
} }
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners // Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer) err = peerConnection.SetLocalDescription(answer)
if err != nil { if err != nil {
panic(err) 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 // 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 {} select {}
} }

19
go.mod
View File

@@ -3,12 +3,15 @@ module github.com/pion/mediadevices
go 1.13 go 1.13
require ( require (
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
github.com/faiface/beep v1.0.2 github.com/gen2brain/malgo v0.10.35
github.com/jfreymuth/pulse v0.0.0-20200424182717-3b0820ad352f github.com/google/uuid v1.3.0
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4 github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
github.com/pion/webrtc/v2 v2.2.8 github.com/pion/interceptor v0.1.12
github.com/satori/go.uuid v1.2.0 github.com/pion/logging v0.2.2
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 github.com/pion/rtcp v1.2.10
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 github.com/pion/rtp v1.7.13
github.com/pion/webrtc/v3 v3.1.43
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )

261
go.sum
View File

@@ -1,147 +1,180 @@
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs= github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4= github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ= github.com/gen2brain/malgo v0.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 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.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
github.com/jfreymuth/pulse v0.0.0-20200424182717-3b0820ad352f h1:XyMNiJ5vCUTlgl4R/pfw11rzt1sbdzNLbZCk/bb3LfU= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/jfreymuth/pulse v0.0.0-20200424182717-3b0820ad352f/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4 h1:2ydMA2KbxRkYmIw3R8Me8dn90bejxBR4MKYXJ5THK3I= github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4/go.mod h1:v9KQvlDYMuvlwniumBVMlrB0VHQvyTgxNvaXjPmTmps= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pion/datachannel v1.4.16 h1:dvuDC0IBMUDQvwO+gRu0Dv+W5j7rrgNpCmtheb6iYnc= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pion/datachannel v1.4.16/go.mod h1:gRGhxZv7X2/30Qxes4WEXtimKBXcwj/3WsDtBlHnvJY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/dtls/v2 v2.0.0 h1:Fk+MBhLZ/U1bImzAhmzwbO/pP2rKhtTw8iA934H3ybE= github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
github.com/pion/dtls/v2 v2.0.0/go.mod h1:VkY5VL2wtsQQOG60xQ4lkV5pdn0wwBBTzCfRJqXhp3A= github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
github.com/pion/ice v0.7.14 h1:lin/tzVc562t0Qk62/JlfOMX/RWuUSq/YyXakH2HTTQ= github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/ice v0.7.14/go.mod h1:/Lz6jAUhsvXed7kNJImXtvVSgjtcdGKoZAZIYb9WEm0= github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY= github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak= github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM= github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
github.com/pion/rtp v1.3.2 h1:Yfzf1mU4Zmg7XWHitzYe2i+l+c68iO+wshzIUW44p1c= github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtp v1.3.2/go.mod h1:q9wPnA96pu2urCcW/sK/RiDn597bhGoAQQ+y2fDwHuY= github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
github.com/pion/rtp v1.4.0 h1:EkeHEXKuJhZoRUxtL2Ie80vVg9gBH+poT9UoL8M14nw= github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.4.0/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4= github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8= github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v2 v2.3.7 h1:WUZHI3pfiYCaE8UGUYcabk863LCK+Bq3AklV5O0oInQ= github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
github.com/pion/sdp/v2 v2.3.7/go.mod h1:+ZZf35r1+zbaWYiZLfPutWfx58DAWcGb2QsS3D/s9M8= github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp v1.3.1 h1:WNDLN41ST0P6cXRpzx97JJW//vChAEo1+Etdqo+UMnM= github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
github.com/pion/srtp v1.3.1/go.mod h1:nxEytDDGTN+eNKJ1l5gzOCWQFuksgijorsSlgEjc40Y= github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
github.com/pion/stun v0.3.3 h1:brYuPl9bN9w/VM7OdNzRSLoqsnwlyNvD9MVeJrHjDQw= github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80= github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
github.com/pion/turn/v2 v2.0.3 h1:SJUUIbcPoehlyZgMyIUbBBDhI03sBx32x3JuSIBKBWA= github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.0.3/go.mod h1:kl1hmT3NxcLynpXVnwJgObL8C9NaCyPTeqI2DcCpSZs= github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/webrtc/v2 v2.2.8 h1:vCSPnXmERhJTNfkPztkEQb8YKI1jrtGSK9e7/aZ4jOc= github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/webrtc/v2 v2.2.8/go.mod h1:Zl5bY5AGfc9gW0U20VSGHUKbiDcfuRDEmsb7cte8cwk= github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pion/webrtc/v3 v3.1.43 h1:YT3ZTO94UT4kSBvZnRAH82+0jJPUruiKr9CEstdlQzk=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pion/webrtc/v3 v3.1.43/go.mod h1:G/J8k0+grVsjC/rjCZ24AKoCCxcFFODgh7zThNZGs0M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/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-20200226121028-0de0cce0169b/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-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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-20211216021012-1d35b9e2eb4e/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-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 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/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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= 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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 MiB

View File

@@ -11,12 +11,15 @@ import (
func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) { func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
var n, totalBytes int var n, totalBytes int
var err error var err error
buf := make([]byte, 1024) buf := make([]byte, 1024)
start := time.Now() start := time.Now()
now := start now := start
end := now.Add(dur) end := now.Add(dur)
for now.Before(end) { for {
n, err = r.Read(buf) n, err = r.Read(buf)
now = time.Now()
if err != nil { if err != nil {
if e, ok := err.(*mio.InsufficientBufferError); ok { if e, ok := err.(*mio.InsufficientBufferError); ok {
buf = make([]byte, 2*e.RequiredSize) buf = make([]byte, 2*e.RequiredSize)
@@ -24,6 +27,7 @@ func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
} }
if err == io.EOF { if err == io.EOF {
dur = now.Sub(start)
totalBytes += n totalBytes += n
break break
} }
@@ -31,11 +35,12 @@ func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
return 0, err return 0, err
} }
totalBytes += n if now.After(end) {
now = time.Now() break
}
totalBytes += n // count bytes if the data arrived within the period
} }
elapsed := time.Now().Sub(start).Seconds() avg := float64(totalBytes*8) / dur.Seconds()
avg := float64(totalBytes*8) / elapsed
return avg, nil return avg, nil
} }

View File

@@ -2,25 +2,38 @@ package codec
import ( import (
"io" "io"
"runtime"
"sync" "sync"
"testing" "testing"
"time" "time"
) )
func TestMeasureBitRateStatic(t *testing.T) { func TestMeasureBitRateStatic(t *testing.T) {
// https://github.com/pion/mediadevices/issues/198
if runtime.GOOS == "darwin" {
t.Skip("Skipping because Darwin CI is not reliable for timing related tests.")
}
r, w := io.Pipe() r, w := io.Pipe()
dur := time.Second * 5 const (
dataSize := 1000 dataSize = 1000
var precision float64 = 8 // 1 byte dur = 5 * time.Second
packetInterval = time.Second
precision = 8.0 // 1 byte
)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
data := make([]byte, dataSize) data := make([]byte, dataSize)
ticker := time.NewTicker(packetInterval)
// Wait half interval
time.Sleep(packetInterval / 2)
// Make sure that this goroutine is synchronized with main goroutine // Make sure that this goroutine is synchronized with main goroutine
wg.Done() wg.Done()
ticker := time.NewTicker(time.Second)
for { for {
select { select {
@@ -47,31 +60,40 @@ func TestMeasureBitRateStatic(t *testing.T) {
} }
func TestMeasureBitRateDynamic(t *testing.T) { func TestMeasureBitRateDynamic(t *testing.T) {
// https://github.com/pion/mediadevices/issues/198
if runtime.GOOS == "darwin" {
t.Skip("Skipping because Darwin CI is not reliable for timing related tests.")
}
r, w := io.Pipe() r, w := io.Pipe()
dur := time.Second * 5 const (
dataSize := 1000 dataSize = 1000
var precision float64 = 8 // 1 byte dur = 5 * time.Second
packetInterval = time.Millisecond * 250
precision = 8.0 // 1 byte
)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
data := make([]byte, dataSize) data := make([]byte, dataSize)
wg.Done() ticker := time.NewTicker(packetInterval)
ticker := time.NewTicker(time.Millisecond * 500)
var count int
// Wait half interval
time.Sleep(packetInterval / 2)
wg.Done()
var count int
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
// 4 x 500ms ticks and 250ms ticks
if count%2 == 1 || count >= 8 {
w.Write(data) w.Write(data)
count++
// Wait until 4 slow ticks, which is also equal to 2 seconds
if count == 4 {
ticker.Stop()
// Speed up the tick by 2 times for the rest
ticker = time.NewTicker(time.Millisecond * 250)
} }
count++
case <-done: case <-done:
w.Close() w.Close()
return return

View File

@@ -0,0 +1,11 @@
package logging
import (
"github.com/pion/logging"
)
var loggerFactory = logging.NewDefaultLoggerFactory()
func NewLogger(scope string) logging.LeveledLogger {
return loggerFactory.NewLogger(scope)
}

75
ioreader.go Normal file
View File

@@ -0,0 +1,75 @@
package mediadevices
import "github.com/pion/mediadevices/pkg/codec"
type EncodedBuffer struct {
Data []byte
Samples uint32
}
type EncodedReadCloser interface {
Read() (EncodedBuffer, func(), error)
Close() error
codec.Controllable
}
type encodedReadCloserImpl struct {
readFn func() (EncodedBuffer, func(), error)
closeFn func() error
controllerFn func() codec.EncoderController
}
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
return r.readFn()
}
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
controller func() codec.EncoderController
}
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
var encoded EncodedBuffer
release := func() {}
return &encodedIOReadCloserImpl{
readFn: func(b []byte) (int, error) {
var err error
if len(encoded.Data) == 0 {
release()
encoded, release, err = reader.Read()
if err != nil {
reader.Close()
return 0, err
}
}
n := copy(b, encoded.Data)
encoded.Data = encoded.Data[n:]
return n, nil
},
closeFn: reader.Close,
controller: reader.Controller,
}
}
func (r *encodedIOReadCloserImpl) Read(b []byte) (int, error) {
return r.readFn(b)
}
func (r *encodedIOReadCloserImpl) Close() error {
return r.closeFn()
}
func (r *encodedIOReadCloserImpl) Controller() codec.EncoderController {
return r.controller()
}

View File

@@ -1,10 +0,0 @@
FROM dockercore/golang-cross
RUN apt-get update -qq \
&& apt-get install -y \
g++-mingw-w64 \
nasm \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
VOLUME /go/src/github.com/pion/mediadevices
WORKDIR /go/src/github.com/pion/mediadevices

7
logging.go Normal file
View File

@@ -0,0 +1,7 @@
package mediadevices
import (
"github.com/pion/mediadevices/internal/logging"
)
var logger = logging.NewLogger("mediadevices")

View File

@@ -7,7 +7,7 @@ type MediaDeviceType int
// MediaDeviceType definitions. // MediaDeviceType definitions.
const ( const (
VideoInput MediaDeviceType = iota VideoInput MediaDeviceType = iota + 1
AudioInput AudioInput
AudioOutput AudioOutput
) )

View File

@@ -3,98 +3,30 @@ package mediadevices
import ( import (
"fmt" "fmt"
"math" "math"
"strings"
"github.com/pion/mediadevices/pkg/driver" "github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
) )
var errNotFound = fmt.Errorf("failed to find the best driver that fits the constraints") var errNotFound = fmt.Errorf("failed to find the best driver that fits the constraints")
// MediaDevices is an interface that's defined on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
type MediaDevices interface {
GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error)
GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error)
EnumerateDevices() []MediaDeviceInfo
}
// NewMediaDevices creates MediaDevices interface that provides access to connected media input devices
// like cameras and microphones, as well as screen sharing.
// In essence, it lets you obtain access to any hardware source of media data.
func NewMediaDevices(pc *webrtc.PeerConnection, opts ...MediaDevicesOption) MediaDevices {
codecs := make(map[webrtc.RTPCodecType][]*webrtc.RTPCodec)
for _, kind := range []webrtc.RTPCodecType{
webrtc.RTPCodecTypeAudio,
webrtc.RTPCodecTypeVideo,
} {
codecs[kind] = pc.GetRegisteredRTPCodecs(kind)
}
return NewMediaDevicesFromCodecs(codecs, opts...)
}
// NewMediaDevicesFromCodecs creates MediaDevices interface from lists of the available codecs
// that provides access to connected media input devices like cameras and microphones,
// as well as screen sharing.
// In essence, it lets you obtain access to any hardware source of media data.
func NewMediaDevicesFromCodecs(codecs map[webrtc.RTPCodecType][]*webrtc.RTPCodec, opts ...MediaDevicesOption) MediaDevices {
mdo := MediaDevicesOptions{
codecs: codecs,
trackGenerator: defaultTrackGenerator,
}
for _, o := range opts {
o(&mdo)
}
return &mediaDevices{
MediaDevicesOptions: mdo,
}
}
// TrackGenerator is a function to create new track.
type TrackGenerator func(payloadType uint8, ssrc uint32, id, label string, codec *webrtc.RTPCodec) (LocalTrack, error)
var defaultTrackGenerator = TrackGenerator(func(pt uint8, ssrc uint32, id, label string, codec *webrtc.RTPCodec) (LocalTrack, error) {
return webrtc.NewTrack(pt, ssrc, id, label, codec)
})
type mediaDevices struct {
MediaDevicesOptions
}
// MediaDevicesOptions stores parameters used by MediaDevices.
type MediaDevicesOptions struct {
codecs map[webrtc.RTPCodecType][]*webrtc.RTPCodec
trackGenerator TrackGenerator
}
// MediaDevicesOption is a type of MediaDevices functional option.
type MediaDevicesOption func(*MediaDevicesOptions)
// WithTrackGenerator specifies a TrackGenerator to use customized track.
func WithTrackGenerator(gen TrackGenerator) MediaDevicesOption {
return func(o *MediaDevicesOptions) {
o.trackGenerator = gen
}
}
// GetDisplayMedia prompts the user to select and grant permission to capture the contents // GetDisplayMedia prompts the user to select and grant permission to capture the contents
// of a display or portion thereof (such as a window) as a MediaStream. // of a display or portion thereof (such as a window) as a MediaStream.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia // Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
func (m *mediaDevices) GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) { func GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
trackers := make([]Tracker, 0) trackers := make([]Track, 0)
cleanTrackers := func() { cleanTrackers := func() {
for _, t := range trackers { for _, t := range trackers {
t.Stop() t.Close()
} }
} }
var videoConstraints MediaTrackConstraints var videoConstraints MediaTrackConstraints
if constraints.Video != nil { if constraints.Video != nil {
constraints.Video(&videoConstraints) constraints.Video(&videoConstraints)
} tracker, err := selectScreen(videoConstraints, constraints.Codec)
if videoConstraints.Enabled {
tracker, err := m.selectScreen(videoConstraints)
if err != nil { if err != nil {
cleanTrackers() cleanTrackers()
return nil, err return nil, err
@@ -115,27 +47,20 @@ func (m *mediaDevices) GetDisplayMedia(constraints MediaStreamConstraints) (Medi
// GetUserMedia prompts the user for permission to use a media input which produces a MediaStream // GetUserMedia prompts the user for permission to use a media input which produces a MediaStream
// with tracks containing the requested types of media. // with tracks containing the requested types of media.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia // Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
func (m *mediaDevices) GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) { func GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) {
// TODO: It should return media stream based on constraints // TODO: It should return media stream based on constraints
trackers := make([]Tracker, 0) trackers := make([]Track, 0)
cleanTrackers := func() { cleanTrackers := func() {
for _, t := range trackers { for _, t := range trackers {
t.Stop() t.Close()
} }
} }
var videoConstraints, audioConstraints MediaTrackConstraints var videoConstraints, audioConstraints MediaTrackConstraints
if constraints.Video != nil { if constraints.Video != nil {
constraints.Video(&videoConstraints) constraints.Video(&videoConstraints)
} tracker, err := selectVideo(videoConstraints, constraints.Codec)
if constraints.Audio != nil {
constraints.Audio(&audioConstraints)
}
if videoConstraints.Enabled {
tracker, err := m.selectVideo(videoConstraints)
if err != nil { if err != nil {
cleanTrackers() cleanTrackers()
return nil, err return nil, err
@@ -144,8 +69,9 @@ func (m *mediaDevices) GetUserMedia(constraints MediaStreamConstraints) (MediaSt
trackers = append(trackers, tracker) trackers = append(trackers, tracker)
} }
if audioConstraints.Enabled { if constraints.Audio != nil {
tracker, err := m.selectAudio(audioConstraints) constraints.Audio(&audioConstraints)
tracker, err := selectAudio(audioConstraints, constraints.Codec)
if err != nil { if err != nil {
cleanTrackers() cleanTrackers()
return nil, err return nil, err
@@ -194,13 +120,20 @@ func queryDriverProperties(filter driver.FilterFn) map[driver.Driver][]prop.Medi
func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints) (driver.Driver, MediaTrackConstraints, error) { func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints) (driver.Driver, MediaTrackConstraints, error) {
var bestDriver driver.Driver var bestDriver driver.Driver
var bestProp prop.Media var bestProp prop.Media
var foundPropertiesLog []string
minFitnessDist := math.Inf(1) minFitnessDist := math.Inf(1)
foundPropertiesLog = append(foundPropertiesLog, "\n============ Found Properties ============")
driverProperties := queryDriverProperties(filter) driverProperties := queryDriverProperties(filter)
for d, props := range driverProperties { for d, props := range driverProperties {
priority := float64(d.Info().Priority) priority := float64(d.Info().Priority)
for _, p := range props { for _, p := range props {
fitnessDist := constraints.Media.FitnessDistance(p) - priority foundPropertiesLog = append(foundPropertiesLog, p.String())
fitnessDist, ok := constraints.MediaConstraints.FitnessDistance(p)
if !ok {
continue
}
fitnessDist -= priority
if fitnessDist < minFitnessDist { if fitnessDist < minFitnessDist {
minFitnessDist = fitnessDist minFitnessDist = fitnessDist
bestDriver = d bestDriver = d
@@ -209,64 +142,61 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
} }
} }
foundPropertiesLog = append(foundPropertiesLog, "=============== Constraints ==============")
foundPropertiesLog = append(foundPropertiesLog, constraints.String())
foundPropertiesLog = append(foundPropertiesLog, "================ Best Fit ================")
if bestDriver == nil { if bestDriver == nil {
foundPropertiesLog = append(foundPropertiesLog, "Not found")
logger.Debug(strings.Join(foundPropertiesLog, "\n\n"))
return nil, MediaTrackConstraints{}, errNotFound return nil, MediaTrackConstraints{}, errNotFound
} }
constraints.Merge(bestProp) foundPropertiesLog = append(foundPropertiesLog, bestProp.String())
logger.Debug(strings.Join(foundPropertiesLog, "\n\n"))
constraints.selectedMedia = prop.Media{}
constraints.selectedMedia.MergeConstraints(constraints.MediaConstraints)
constraints.selectedMedia.Merge(bestProp)
return bestDriver, constraints, nil return bestDriver, constraints, nil
} }
func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker, error) { func selectAudio(constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
typeFilter := driver.FilterAudioRecorder() typeFilter := driver.FilterAudioRecorder()
filter := typeFilter
if constraints.DeviceID != "" {
idFilter := driver.FilterID(constraints.DeviceID)
filter = driver.FilterAnd(typeFilter, idFilter)
}
d, c, err := selectBestDriver(filter, constraints) d, c, err := selectBestDriver(typeFilter, constraints)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newTrack(&m.MediaDevicesOptions, d, c) return newTrackFromDriver(d, c, selector)
} }
func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker, error) { func selectVideo(constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
typeFilter := driver.FilterVideoRecorder() typeFilter := driver.FilterVideoRecorder()
notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen)) notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen))
filter := driver.FilterAnd(typeFilter, notScreenFilter) filter := driver.FilterAnd(typeFilter, notScreenFilter)
if constraints.DeviceID != "" {
idFilter := driver.FilterID(constraints.DeviceID)
filter = driver.FilterAnd(typeFilter, notScreenFilter, idFilter)
}
d, c, err := selectBestDriver(filter, constraints) d, c, err := selectBestDriver(filter, constraints)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newTrack(&m.MediaDevicesOptions, d, c) return newTrackFromDriver(d, c, selector)
} }
func (m *mediaDevices) selectScreen(constraints MediaTrackConstraints) (Tracker, error) { func selectScreen(constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
typeFilter := driver.FilterVideoRecorder() typeFilter := driver.FilterVideoRecorder()
screenFilter := driver.FilterDeviceType(driver.Screen) screenFilter := driver.FilterDeviceType(driver.Screen)
filter := driver.FilterAnd(typeFilter, screenFilter) filter := driver.FilterAnd(typeFilter, screenFilter)
if constraints.DeviceID != "" {
idFilter := driver.FilterID(constraints.DeviceID)
filter = driver.FilterAnd(typeFilter, screenFilter, idFilter)
}
d, c, err := selectBestDriver(filter, constraints) d, c, err := selectBestDriver(filter, constraints)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newTrack(&m.MediaDevicesOptions, d, c) return newTrackFromDriver(d, c, selector)
} }
func (m *mediaDevices) EnumerateDevices() []MediaDeviceInfo { func EnumerateDevices() []MediaDeviceInfo {
drivers := driver.GetManager().Query( drivers := driver.GetManager().Query(
driver.FilterFn(func(driver.Driver) bool { return true })) driver.FilterFn(func(driver.Driver) bool { return true }))
info := make([]MediaDeviceInfo, 0, len(drivers)) info := make([]MediaDeviceInfo, 0, len(drivers))

View File

@@ -0,0 +1,82 @@
// +build e2e
package mediadevices
import (
"image"
"sync"
"testing"
"github.com/pion/mediadevices/pkg/codec/x264"
"github.com/pion/mediadevices/pkg/frame"
)
type mockVideoSource struct {
width, height int
pool sync.Pool
decoder frame.Decoder
}
func newMockVideoSource(width, height int) *mockVideoSource {
decoder, err := frame.NewDecoder(frame.FormatYUY2)
if err != nil {
panic(err)
}
return &mockVideoSource{
width: width,
height: height,
pool: sync.Pool{
New: func() interface{} {
resolution := width * height
return make([]byte, resolution*2)
},
},
decoder: decoder,
}
}
func (source *mockVideoSource) ID() string { return "" }
func (source *mockVideoSource) Close() error { return nil }
func (source *mockVideoSource) Read() (image.Image, func(), error) {
raw := source.pool.Get().([]byte)
decoded, release, err := source.decoder.Decode(raw, source.width, source.height)
source.pool.Put(raw)
if err != nil {
return nil, nil, err
}
return decoded, release, nil
}
func BenchmarkEndToEnd(b *testing.B) {
params, err := x264.NewParams()
if err != nil {
b.Fatal(err)
}
params.BitRate = 300_000
videoSource := newMockVideoSource(1920, 1080)
track := NewVideoTrack(videoSource, nil).(*VideoTrack)
defer track.Close()
reader := track.NewReader(false)
inputProp, err := detectCurrentVideoProp(track.Broadcaster)
if err != nil {
b.Fatal(err)
}
encodedReader, err := params.BuildVideoEncoder(reader, inputProp)
if err != nil {
b.Fatal(err)
}
defer encodedReader.Close()
for i := 0; i < b.N; i++ {
_, release, err := encodedReader.Read()
if err != nil {
b.Fatal(err)
}
release()
}
}

View File

@@ -1,90 +1,43 @@
package mediadevices package mediadevices
import ( import (
"errors"
"io" "io"
"testing" "testing"
"time" "time"
"github.com/pion/webrtc/v2" "github.com/pion/mediadevices/pkg/driver"
"github.com/pion/webrtc/v2/pkg/media"
"github.com/pion/mediadevices/pkg/codec"
_ "github.com/pion/mediadevices/pkg/driver/audiotest" _ "github.com/pion/mediadevices/pkg/driver/audiotest"
_ "github.com/pion/mediadevices/pkg/driver/videotest" _ "github.com/pion/mediadevices/pkg/driver/videotest"
"github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
) )
func TestGetUserMedia(t *testing.T) { func TestGetUserMedia(t *testing.T) {
videoParams := mockParams{
BaseParams: codec.BaseParams{
BitRate: 100000,
},
name: "MockVideo",
}
audioParams := mockParams{
BaseParams: codec.BaseParams{
BitRate: 32000,
},
name: "MockAudio",
}
md := NewMediaDevicesFromCodecs(
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
webrtc.RTPCodecTypeVideo: []*webrtc.RTPCodec{
&webrtc.RTPCodec{Type: webrtc.RTPCodecTypeVideo, Name: "MockVideo", PayloadType: 1},
},
webrtc.RTPCodecTypeAudio: []*webrtc.RTPCodec{
&webrtc.RTPCodec{Type: webrtc.RTPCodecTypeAudio, Name: "MockAudio", PayloadType: 2},
},
},
WithTrackGenerator(
func(_ uint8, _ uint32, id, _ string, codec *webrtc.RTPCodec) (
LocalTrack, error,
) {
return newMockTrack(codec, id), nil
},
),
)
constraints := MediaStreamConstraints{ constraints := MediaStreamConstraints{
Video: func(c *MediaTrackConstraints) { Video: func(c *MediaTrackConstraints) {
c.Enabled = true c.Width = prop.Int(640)
c.Width = 640 c.Height = prop.Int(480)
c.Height = 480
params := videoParams
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
}, },
Audio: func(c *MediaTrackConstraints) { Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
}, },
} }
constraintsWrong := MediaStreamConstraints{ constraintsWrong := MediaStreamConstraints{
Video: func(c *MediaTrackConstraints) { Video: func(c *MediaTrackConstraints) {
c.Enabled = true c.Width = prop.IntExact(10000)
c.Width = 640 c.Height = prop.Int(480)
c.Height = 480
params := videoParams
params.BitRate = 0
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
}, },
Audio: func(c *MediaTrackConstraints) { Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
}, },
} }
// GetUserMedia with broken parameters // GetUserMedia with broken parameters
ms, err := md.GetUserMedia(constraintsWrong) ms, err := GetUserMedia(constraintsWrong)
if err == nil { if err == nil {
t.Fatal("Expected error, but got nil") t.Fatal("Expected error, but got nil")
} }
// GetUserMedia with correct parameters // GetUserMedia with correct parameters
ms, err = md.GetUserMedia(constraints) ms, err = GetUserMedia(constraints)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
@@ -102,11 +55,11 @@ func TestGetUserMedia(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
for _, track := range tracks { for _, track := range tracks {
track.Stop() track.Close()
} }
// Stop and retry GetUserMedia // Stop and retry GetUserMedia
ms, err = md.GetUserMedia(constraints) ms, err = GetUserMedia(constraints)
if err != nil { if err != nil {
t.Fatalf("Failed to GetUserMedia after the previsous tracks stopped: %v", err) t.Fatalf("Failed to GetUserMedia after the previsous tracks stopped: %v", err)
} }
@@ -122,98 +75,168 @@ func TestGetUserMedia(t *testing.T) {
}) })
} }
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
} for _, track := range tracks {
track.Close()
type mockTrack struct {
codec *webrtc.RTPCodec
id string
}
func newMockTrack(codec *webrtc.RTPCodec, id string) *mockTrack {
return &mockTrack{
codec: codec,
id: id,
} }
} }
func (t *mockTrack) WriteSample(s media.Sample) error { func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
return nil filterFn := driver.FilterVideoRecorder()
drivers := driver.GetManager().Query(filterFn)
if len(drivers) == 0 {
t.Fatal("expect to get at least 1 driver")
} }
func (t *mockTrack) Codec() *webrtc.RTPCodec { driver := drivers[0]
return t.codec err := driver.Open()
if err != nil {
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]
// 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,
},
} }
func (t *mockTrack) ID() string { for name, c := range cases {
return t.id 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,
},
} }
func (t *mockTrack) Kind() webrtc.RTPCodecType { bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
return t.codec.Type if err != nil {
t.Fatal(err)
} }
type mockParams struct { if driver != bestDriver {
codec.BaseParams t.Fatal("best driver is not expected")
name string
} }
func (params *mockParams) Name() string { s := bestConstraints.selectedMedia
return params.name 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 (params *mockParams) BuildVideoEncoder(r video.Reader, p prop.Media) (codec.ReadCloser, error) { })
if params.BitRate == 0 {
// This is a dummy error to test the failure condition.
return nil, errors.New("wrong codec parameter")
}
return &mockVideoCodec{
r: r,
closed: make(chan struct{}),
}, nil
} }
func (params *mockParams) BuildAudioEncoder(r audio.Reader, p prop.Media) (codec.ReadCloser, error) {
return &mockAudioCodec{
r: r,
closed: make(chan struct{}),
}, nil
} }
type mockCodec struct{} func TestSelectBestDriverConstraintsNoFit(t *testing.T) {
filterFn := driver.FilterVideoRecorder()
func (e *mockCodec) SetBitRate(b int) error { drivers := driver.GetManager().Query(filterFn)
return nil if len(drivers) == 0 {
t.Fatal("expect to get at least 1 driver")
} }
func (e *mockCodec) ForceKeyFrame() error { driver := drivers[0]
return nil err := driver.Open()
if err != nil {
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,
},
} }
type mockVideoCodec struct { for name, c := range cases {
mockCodec c := c
r video.Reader t.Run(name, func(t *testing.T) {
closed chan struct{} 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),
},
},
} }
func (m *mockVideoCodec) Read(b []byte) (int, error) { _, _, err := selectBestDriver(filterFn, wantConstraints)
if _, err := m.r.Read(); err != nil { if err == nil {
return 0, err t.Fatal("expect to not find a driver that fits the constraints")
} }
return len(b), nil })
} }
func (m *mockVideoCodec) Close() error { return nil }
type mockAudioCodec struct {
mockCodec
r audio.Reader
closed chan struct{}
} }
func (m *mockAudioCodec) Read(b []byte) (int, error) {
if _, err := m.r.Read(); err != nil {
return 0, err
}
return len(b), nil
}
func (m *mockAudioCodec) Close() error { return nil }

View File

@@ -3,88 +3,86 @@ package mediadevices
import ( import (
"sync" "sync"
"github.com/pion/webrtc/v2" "github.com/pion/webrtc/v3"
) )
// MediaStream is an interface that represents a collection of existing tracks. // MediaStream is an interface that represents a collection of existing tracks.
type MediaStream interface { type MediaStream interface {
// GetAudioTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks // GetAudioTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
GetAudioTracks() []Tracker GetAudioTracks() []Track
// GetVideoTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks // GetVideoTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
GetVideoTracks() []Tracker GetVideoTracks() []Track
// GetTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks // GetTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
GetTracks() []Tracker GetTracks() []Track
// AddTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack // AddTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
AddTrack(t Tracker) AddTrack(t Track)
// RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack // RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
RemoveTrack(t Tracker) RemoveTrack(t Track)
} }
type mediaStream struct { type mediaStream struct {
trackers map[string]Tracker tracks map[Track]struct{}
l sync.RWMutex l sync.RWMutex
} }
const rtpCodecTypeDefault webrtc.RTPCodecType = 0 const trackTypeDefault webrtc.RTPCodecType = 0
// NewMediaStream creates a MediaStream interface that's defined in // NewMediaStream creates a MediaStream interface that's defined in
// https://w3c.github.io/mediacapture-main/#dom-mediastream // https://w3c.github.io/mediacapture-main/#dom-mediastream
func NewMediaStream(trackers ...Tracker) (MediaStream, error) { func NewMediaStream(tracks ...Track) (MediaStream, error) {
m := mediaStream{trackers: make(map[string]Tracker)} m := mediaStream{tracks: make(map[Track]struct{})}
for _, tracker := range trackers { for _, track := range tracks {
id := tracker.LocalTrack().ID() if _, ok := m.tracks[track]; !ok {
if _, ok := m.trackers[id]; !ok { m.tracks[track] = struct{}{}
m.trackers[id] = tracker
} }
} }
return &m, nil return &m, nil
} }
func (m *mediaStream) GetAudioTracks() []Tracker { func (m *mediaStream) GetAudioTracks() []Track {
return m.queryTracks(webrtc.RTPCodecTypeAudio) return m.queryTracks(webrtc.RTPCodecTypeAudio)
} }
func (m *mediaStream) GetVideoTracks() []Tracker { func (m *mediaStream) GetVideoTracks() []Track {
return m.queryTracks(webrtc.RTPCodecTypeVideo) return m.queryTracks(webrtc.RTPCodecTypeVideo)
} }
func (m *mediaStream) GetTracks() []Tracker { func (m *mediaStream) GetTracks() []Track {
return m.queryTracks(rtpCodecTypeDefault) return m.queryTracks(trackTypeDefault)
} }
// queryTracks returns all tracks that are the same kind as t. // queryTracks returns all tracks that are the same kind as t.
// If t is 0, which is the default, queryTracks will return all the tracks. // If t is 0, which is the default, queryTracks will return all the tracks.
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Tracker { func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Track {
m.l.RLock() m.l.RLock()
defer m.l.RUnlock() defer m.l.RUnlock()
result := make([]Tracker, 0) result := make([]Track, 0)
for _, tracker := range m.trackers { for track := range m.tracks {
if tracker.LocalTrack().Kind() == t || t == rtpCodecTypeDefault { if track.Kind() == t || t == trackTypeDefault {
result = append(result, tracker) result = append(result, track)
} }
} }
return result return result
} }
func (m *mediaStream) AddTrack(t Tracker) { func (m *mediaStream) AddTrack(t Track) {
m.l.Lock() m.l.Lock()
defer m.l.Unlock() defer m.l.Unlock()
id := t.LocalTrack().ID() if _, ok := m.tracks[t]; ok {
if _, ok := m.trackers[id]; ok {
return return
} }
m.trackers[id] = t m.tracks[t] = struct{}{}
} }
func (m *mediaStream) RemoveTrack(t Tracker) { func (m *mediaStream) RemoveTrack(t Track) {
m.l.Lock() m.l.Lock()
defer m.l.Unlock() defer m.l.Unlock()
delete(m.trackers, t.LocalTrack().ID()) delete(m.tracks, t)
} }

116
mediastream_test.go Normal file
View File

@@ -0,0 +1,116 @@
package mediadevices
import (
"io"
"testing"
"github.com/pion/webrtc/v3"
)
type mockMediaStreamTrack struct {
kind MediaDeviceType
}
func (track *mockMediaStreamTrack) ID() string {
return ""
}
func (track *mockMediaStreamTrack) StreamID() string {
return ""
}
func (track *mockMediaStreamTrack) RID() string {
return ""
}
func (track *mockMediaStreamTrack) Close() error {
return nil
}
func (track *mockMediaStreamTrack) Kind() webrtc.RTPCodecType {
switch track.kind {
case AudioInput:
return webrtc.RTPCodecTypeAudio
case VideoInput:
return webrtc.RTPCodecTypeVideo
default:
panic("invalid track kind")
}
}
func (track *mockMediaStreamTrack) OnEnded(handler func(error)) {
}
func (track *mockMediaStreamTrack) Bind(ctx webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) {
return webrtc.RTPCodecParameters{}, nil
}
func (track *mockMediaStreamTrack) Unbind(ctx webrtc.TrackLocalContext) error {
return nil
}
func (track *mockMediaStreamTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (RTPReadCloser, error) {
return nil, nil
}
func (track *mockMediaStreamTrack) NewEncodedReader(codecName string) (EncodedReadCloser, error) {
return nil, nil
}
func (track *mockMediaStreamTrack) NewEncodedIOReader(codecName string) (io.ReadCloser, error) {
return nil, nil
}
func TestMediaStreamFilters(t *testing.T) {
audioTracks := []Track{
&mockMediaStreamTrack{AudioInput},
&mockMediaStreamTrack{AudioInput},
&mockMediaStreamTrack{AudioInput},
&mockMediaStreamTrack{AudioInput},
&mockMediaStreamTrack{AudioInput},
}
videoTracks := []Track{
&mockMediaStreamTrack{VideoInput},
&mockMediaStreamTrack{VideoInput},
&mockMediaStreamTrack{VideoInput},
}
tracks := append(audioTracks, videoTracks...)
stream, err := NewMediaStream(tracks...)
if err != nil {
t.Fatal(err)
}
expect := func(t *testing.T, actual, expected []Track) {
if len(actual) != len(expected) {
t.Fatalf("%s: Expected to get %d trackers, but got %d trackers", t.Name(), len(expected), len(actual))
}
for _, a := range actual {
found := false
for _, e := range expected {
if e == a {
found = true
break
}
}
if !found {
t.Fatalf("%s: Expected to find %p in the query results", t.Name(), a)
}
}
}
t.Run("GetAudioTracks", func(t *testing.T) {
expect(t, stream.GetAudioTracks(), audioTracks)
})
t.Run("GetVideoTracks", func(t *testing.T) {
expect(t, stream.GetVideoTracks(), videoTracks)
})
t.Run("GetTracks", func(t *testing.T) {
expect(t, stream.GetTracks(), tracks)
})
}

View File

@@ -1,39 +1,19 @@
package mediadevices package mediadevices
import ( import (
"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/prop"
) )
type MediaStreamConstraints struct { type MediaStreamConstraints struct {
Audio MediaOption Audio MediaOption
Video MediaOption Video MediaOption
Codec *CodecSelector
} }
// MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints // MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints
type MediaTrackConstraints struct { type MediaTrackConstraints struct {
prop.Media prop.MediaConstraints
Enabled bool selectedMedia prop.Media
// VideoEncoderBuilders are codec builders that are used for encoding the video
// and later being used for sending the appropriate RTP payload type.
//
// If one encoder builder fails to build the codec, the next builder will be used,
// repeating until a codec builds. If no builders build successfully, an error is returned.
VideoEncoderBuilders []codec.VideoEncoderBuilder
// AudioEncoderBuilders are codec builders that are used for encoding the audio
// and later being used for sending the appropriate RTP payload type.
//
// If one encoder builder fails to build the codec, the next builder will be used,
// repeating until a codec builds. If no builders build successfully, an error is returned.
AudioEncoderBuilders []codec.AudioEncoderBuilder
// VideoTransform will be used to transform the video that's coming from the driver.
// So, basically it'll look like following: driver -> VideoTransform -> codec
VideoTransform video.TransformFunc
// AudioTransform will be used to transform the audio that's coming from the driver.
// So, basically it'll look like following: driver -> AudioTransform -> code
AudioTransform audio.TransformFunc
} }
type MediaOption func(*MediaTrackConstraints) type MediaOption func(*MediaTrackConstraints)

35
meta.go Normal file
View File

@@ -0,0 +1,35 @@
package mediadevices
import (
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
// detectCurrentVideoProp is a small helper to get current video property
func detectCurrentVideoProp(broadcaster *video.Broadcaster) (prop.Media, error) {
var currentProp prop.Media
// Since broadcaster has a ring buffer internally, a new reader will either read the last
// buffered frame or a new frame from the source. This also implies that no frame will be lost
// in any case.
metaReader := broadcaster.NewReader(false)
metaReader = video.DetectChanges(0, 0, func(p prop.Media) { currentProp = p })(metaReader)
_, _, err := metaReader.Read()
return currentProp, err
}
// detectCurrentAudioProp is a small helper to get current audio property
func detectCurrentAudioProp(broadcaster *audio.Broadcaster) (prop.Media, error) {
var currentProp prop.Media
// Since broadcaster has a ring buffer internally, a new reader will either read the last
// buffered frame or a new frame from the source. This also implies that no frame will be lost
// in any case.
metaReader := broadcaster.NewReader(false)
metaReader = audio.DetectChanges(0, func(p prop.Media) { currentProp = p })(metaReader)
_, _, err := metaReader.Read()
return currentProp, err
}

98
meta_test.go Normal file
View File

@@ -0,0 +1,98 @@
package mediadevices
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/wave"
)
func TestDetectCurrentVideoProp(t *testing.T) {
resolution := image.Rect(0, 0, 4, 4)
first := image.NewRGBA(resolution)
first.Pix[0] = 1
second := image.NewRGBA(resolution)
second.Pix[0] = 2
isFirst := true
source := video.ReaderFunc(func() (image.Image, func(), error) {
if isFirst {
isFirst = true
return first, func() {}, nil
} else {
return second, func() {}, nil
}
})
broadcaster := video.NewBroadcaster(source, nil)
currentProp, err := detectCurrentVideoProp(broadcaster)
if err != nil {
t.Fatal(err)
}
if currentProp.Width != resolution.Dx() {
t.Fatalf("Expect the actual width to be %d, but got %d", currentProp.Width, resolution.Dx())
}
if currentProp.Height != resolution.Dy() {
t.Fatalf("Expect the actual height to be %d, but got %d", currentProp.Height, resolution.Dy())
}
reader := broadcaster.NewReader(false)
img, _, err := reader.Read()
if err != nil {
t.Fatal(err)
}
rgba := img.(*image.RGBA)
if rgba.Pix[0] != 1 {
t.Fatal("Expect the frame after reading the current prop is not the first frame")
}
}
func TestDetectCurrentAudioProp(t *testing.T) {
info := wave.ChunkInfo{
Len: 4,
Channels: 2,
SamplingRate: 48000,
}
first := wave.NewInt16Interleaved(info)
first.Data[0] = 1
second := wave.NewInt16Interleaved(info)
second.Data[0] = 2
isFirst := true
source := audio.ReaderFunc(func() (wave.Audio, func(), error) {
if isFirst {
isFirst = true
return first, func() {}, nil
} else {
return second, func() {}, nil
}
})
broadcaster := audio.NewBroadcaster(source, nil)
currentProp, err := detectCurrentAudioProp(broadcaster)
if err != nil {
t.Fatal(err)
}
if currentProp.ChannelCount != info.Channels {
t.Fatalf("Expect the actual channel count to be %d, but got %d", currentProp.ChannelCount, info.Channels)
}
reader := broadcaster.NewReader(false)
chunk, _, err := reader.Read()
if err != nil {
t.Fatal(err)
}
realChunk := chunk.(*wave.Int16Interleaved)
if realChunk.Data[0] != 1 {
t.Fatal("Expect the chunk after reading the current prop is not the first chunk")
}
}

25
pkg/avfoundation/.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
#User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
.DS_STORE
Build/

View File

@@ -0,0 +1,294 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
F0143CC12479F78E00EC29C9 /* AVFoundationBind.h in Headers */ = {isa = PBXBuildFile; fileRef = F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */; };
F0143CC32479F78E00EC29C9 /* AVFoundationBind.m in Sources */ = {isa = PBXBuildFile; fileRef = F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAVFoundationBind.a; sourceTree = BUILT_PRODUCTS_DIR; };
F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVFoundationBind.h; sourceTree = "<group>"; };
F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVFoundationBind.m; sourceTree = "<group>"; };
F0FDDA0B247E15D900A3429D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
F0143CBB2479F78E00EC29C9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
F0143CB42479F78E00EC29C9 = {
isa = PBXGroup;
children = (
F0143CBF2479F78E00EC29C9 /* AVFoundationBind */,
F0143CBE2479F78E00EC29C9 /* Products */,
F0FDDA0A247E15D900A3429D /* Frameworks */,
);
sourceTree = "<group>";
};
F0143CBE2479F78E00EC29C9 /* Products */ = {
isa = PBXGroup;
children = (
F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */,
);
name = Products;
sourceTree = "<group>";
};
F0143CBF2479F78E00EC29C9 /* AVFoundationBind */ = {
isa = PBXGroup;
children = (
F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */,
F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */,
);
path = AVFoundationBind;
sourceTree = "<group>";
};
F0FDDA0A247E15D900A3429D /* Frameworks */ = {
isa = PBXGroup;
children = (
F0FDDA0B247E15D900A3429D /* AVFoundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
F0143CB92479F78E00EC29C9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
F0143CC12479F78E00EC29C9 /* AVFoundationBind.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
F0143CBC2479F78E00EC29C9 /* AVFoundationBind */ = {
isa = PBXNativeTarget;
buildConfigurationList = F0143CC62479F78E00EC29C9 /* Build configuration list for PBXNativeTarget "AVFoundationBind" */;
buildPhases = (
F0143CB92479F78E00EC29C9 /* Headers */,
F0143CBA2479F78E00EC29C9 /* Sources */,
F0143CBB2479F78E00EC29C9 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = AVFoundationBind;
productName = AVFoundationBind;
productReference = F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
F0143CB52479F78E00EC29C9 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1150;
ORGANIZATIONNAME = "Herman, Lukas";
TargetAttributes = {
F0143CBC2479F78E00EC29C9 = {
CreatedOnToolsVersion = 11.5;
};
};
};
buildConfigurationList = F0143CB82479F78E00EC29C9 /* Build configuration list for PBXProject "AVFoundationBind" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = F0143CB42479F78E00EC29C9;
productRefGroup = F0143CBE2479F78E00EC29C9 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
F0143CBC2479F78E00EC29C9 /* AVFoundationBind */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
F0143CBA2479F78E00EC29C9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F0143CC32479F78E00EC29C9 /* AVFoundationBind.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
F0143CC42479F78E00EC29C9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
F0143CC52479F78E00EC29C9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
F0143CC72479F78E00EC29C9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
F0143CC82479F78E00EC29C9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
F0143CB82479F78E00EC29C9 /* Build configuration list for PBXProject "AVFoundationBind" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F0143CC42479F78E00EC29C9 /* Debug */,
F0143CC52479F78E00EC29C9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F0143CC62479F78E00EC29C9 /* Build configuration list for PBXNativeTarget "AVFoundationBind" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F0143CC72479F78E00EC29C9 /* Debug */,
F0143CC82479F78E00EC29C9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = F0143CB52479F78E00EC29C9 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:AVFoundationBind.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1150"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F0143CBC2479F78E00EC29C9"
BuildableName = "libAVFoundationBind.a"
BlueprintName = "AVFoundationBind"
ReferencedContainer = "container:AVFoundationBind.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F0143CBC2479F78E00EC29C9"
BuildableName = "libAVFoundationBind.a"
BlueprintName = "AVFoundationBind"
ReferencedContainer = "container:AVFoundationBind.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,79 @@
// 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
// 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.
#pragma once
#include <stddef.h>
#define MAX_DEVICES 8
#define MAX_PROPERTIES 64
#define MAX_DEVICE_UID_CHARS 64
typedef const char* STATUS;
static STATUS STATUS_OK = (STATUS) NULL;
static STATUS STATUS_NULL_ARG = (STATUS) "One of the arguments was null";
static STATUS STATUS_DEVICE_INIT_FAILED = (STATUS) "Failed to init device";
static STATUS STATUS_UNSUPPORTED_FRAME_FORMAT = (STATUS) "Unsupported frame format";
static STATUS STATUS_UNSUPPORTED_MEDIA_TYPE = (STATUS) "Unsupported media type";
static STATUS STATUS_FAILED_TO_ACQUIRE_LOCK = (STATUS) "Failed to acquire a lock";
static STATUS STATUS_UNSUPPORTED_FORMAT = (STATUS) "Unsupported device format";
typedef enum AVBindMediaType {
AVBindMediaTypeVideo,
AVBindMediaTypeAudio,
} AVBindMediaType;
typedef enum AVBindFrameFormat {
AVBindFrameFormatI420,
AVBindFrameFormatNV21,
AVBindFrameFormatNV12,
AVBindFrameFormatYUY2,
AVBindFrameFormatUYVY,
} AVBindFrameFormat;
typedef void (*AVBindDataCallback)(void *userData, void *buf, int len);
typedef struct AVBindMediaProperty {
// video property
int width, height;
AVBindFrameFormat frameFormat;
// audio property
} AVBindMediaProperty, *PAVBindMediaProperty;
typedef struct AVBindSession AVBindSession, *PAVBindSession;
typedef struct {
char uid[MAX_DEVICE_UID_CHARS + 1];
} AVBindDevice, *PAVBindDevice;
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
// memory. The caller is expected to not hold on to the address for a long time and make a copy.
// Everytime this function gets called, the array will be overwritten and the memory will be reused.
STATUS AVBindDevices(AVBindMediaType, PAVBindDevice*, int*);
STATUS AVBindSessionInit(AVBindDevice, PAVBindSession*);
STATUS AVBindSessionFree(PAVBindSession*);
STATUS AVBindSessionOpen(PAVBindSession, AVBindMediaProperty, AVBindDataCallback, void*);
STATUS AVBindSessionClose(PAVBindSession);
STATUS AVBindSessionProperties(PAVBindSession, PAVBindMediaProperty*, int*);

View File

@@ -0,0 +1,371 @@
// 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
// 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.
// Naming Convention (let "name" as an actual variable name):
// - mName: "name" is a member of an Objective C object
// - pName: "name" is a C pointer
// - refName: "name" is an Objective C object reference
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "AVFoundationBind.h"
#include <string.h>
#define CHK(condition, status) \
do { \
if(!(condition)) { \
retStatus = status; \
goto cleanup; \
} \
} while(0)
#define CHK_STATUS(status) \
do { \
if(status != STATUS_OK) { \
retStatus = status; \
goto cleanup; \
} \
} while(0)
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property (readonly) AVBindDataCallback mCallback;
@property (readonly) void *mPUserData;
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection;
@end
@implementation VideoDataDelegate
- (id) init: (AVBindDataCallback) callback
withUserData: (void*) pUserData {
self = [super init];
_mCallback = callback;
_mPUserData = pUserData;
return self;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
!CMSampleBufferIsValid(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;
}
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
_mCallback(_mPUserData, buf, (int)dataSize);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
CVBufferRelease(imageBuffer);
}
@end
@interface AudioDataDelegate : NSObject<AVCaptureAudioDataOutputSampleBufferDelegate>
@property (readonly) AVBindDataCallback mCallback;
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection;
@end
@implementation AudioDataDelegate
- (id) init: (AVBindDataCallback) callback {
self = [super init];
_mCallback = callback;
return self;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
// TODO
}
@end
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 AVBindFrameFormatI420:
*pFourCC = kCVPixelFormatType_420YpCbCr8Planar;
break;
case AVBindFrameFormatNV12:
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
break;
case AVBindFrameFormatUYVY:
*pFourCC = kCVPixelFormatType_422YpCbCr8;
break;
case AVBindFrameFormatYUY2:
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
}
return retStatus;
}
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
STATUS retStatus = STATUS_OK;
switch (fourCC) {
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 = AVBindFrameFormatYUY2;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
}
return retStatus;
}
STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *pLen) {
static AVBindDevice devices[MAX_DEVICES];
STATUS retStatus = STATUS_OK;
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
];
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
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';
i++;
}
*ppDevices = devices;
*pLen = i;
cleanup:
[refPool drain];
return retStatus;
}
struct AVBindSession {
AVBindDevice device;
AVCaptureSession *refCaptureSession;
AVBindMediaProperty properties[MAX_PROPERTIES];
};
STATUS AVBindSessionInit(AVBindDevice device, PAVBindSession *ppSessionResult) {
STATUS retStatus = STATUS_OK;
CHK(ppSessionResult != NULL, STATUS_NULL_ARG);
PAVBindSession pSession = malloc(sizeof(AVBindSession));
pSession->device = device;
pSession->refCaptureSession = NULL;
*ppSessionResult = pSession;
cleanup:
return retStatus;
}
STATUS AVBindSessionFree(PAVBindSession *ppSession) {
STATUS retStatus = STATUS_OK;
CHK(ppSession != NULL, STATUS_NULL_ARG);
PAVBindSession pSession = *ppSession;
if (pSession->refCaptureSession != NULL) {
[pSession->refCaptureSession release];
pSession->refCaptureSession = NULL;
}
free(pSession);
*ppSession = NULL;
cleanup:
return retStatus;
}
STATUS AVBindSessionOpen(PAVBindSession pSession,
AVBindMediaProperty property,
AVBindDataCallback dataCallback,
void *pUserData) {
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];
if ([refDevice hasMediaType: AVMediaTypeVideo]) {
VideoDataDelegate *pDelegate = [[VideoDataDelegate alloc]
init: dataCallback
withUserData: pUserData];
AVCaptureVideoDataOutput *pOutput = [[AVCaptureVideoDataOutput alloc] init];
FourCharCode fourCC;
CHK_STATUS(frameFormatToFourCC(property.frameFormat, &fourCC));
pOutput.videoSettings = @{
(id)kCVPixelBufferWidthKey: @(property.width),
(id)kCVPixelBufferHeightKey: @(property.height),
(id)kCVPixelBufferPixelFormatTypeKey: @(fourCC),
};
pOutput.alwaysDiscardsLateVideoFrames = YES;
dispatch_queue_t queue =
dispatch_queue_create("captureQueue", DISPATCH_QUEUE_SERIAL);
[pOutput setSampleBufferDelegate:pDelegate queue:queue];
[refCaptureSession addOutput: pOutput];
} else {
// TODO: implement audio pipeline
}
pSession->refCaptureSession = [refCaptureSession retain];
[refCaptureSession startRunning];
cleanup:
[refPool drain];
return retStatus;
}
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;
CMVideoFormatDescriptionRef videoFormat;
CMVideoDimensions videoDimensions;
memset(pSession->properties, 0, sizeof(pSession->properties));
PAVBindMediaProperty pProperty = pSession->properties;
int len = 0;
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;
pProperty->width = videoDimensions.width;
} else {
// TODO: Get audio properties
}
pProperty++;
len++;
}
*ppProperties = pSession->properties;
*pLen = len;
cleanup:
[refPool drain];
return retStatus;
}

View File

@@ -0,0 +1,54 @@
package avfoundation
import "C"
import (
"sync"
"unsafe"
)
var mu sync.Mutex
var nextID handleID
type dataCb func(data []byte)
var handles = make(map[handleID]dataCb)
type handleID int
//export onData
func onData(userData unsafe.Pointer, buf unsafe.Pointer, length C.int) {
handleNum := (*C.int)(userData)
cb, ok := lookup(handleID(*handleNum))
if ok {
data := C.GoBytes(buf, length)
cb(data)
}
}
func register(fn dataCb) handleID {
mu.Lock()
defer mu.Unlock()
nextID++
for handles[nextID] != nil {
nextID++
}
handles[nextID] = fn
return nextID
}
func lookup(i handleID) (cb dataCb, ok bool) {
mu.Lock()
defer mu.Unlock()
cb, ok = handles[i]
return
}
func unregister(i handleID) {
mu.Lock()
defer mu.Unlock()
delete(handles, i)
}

View File

@@ -0,0 +1,258 @@
// Package avfoundation provides AVFoundation binding for Go
package avfoundation
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo
// #include "AVFoundationBind/AVFoundationBind.h"
// #include "AVFoundationBind/AVFoundationBind.m"
// extern void onData(void*, void*, int);
// void onDataBridge(void *userData, void *buf, int len) {
// onData(userData, buf, len);
// }
import "C"
import (
"context"
"fmt"
"io"
"sync"
"unsafe"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
type MediaType C.AVBindMediaType
const (
Video = MediaType(C.AVBindMediaTypeVideo)
Audio = MediaType(C.AVBindMediaTypeAudio)
)
// Device represents a metadata that later can be used to retrieve back the
// underlying device given by AVFoundation
type Device struct {
// UID is a unique identifier for a device
UID string
cDevice C.AVBindDevice
}
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
switch f {
case frame.FormatI420:
return C.AVBindFrameFormatI420, true
case frame.FormatNV21:
return C.AVBindFrameFormatNV21, true
case frame.FormatNV12:
return C.AVBindFrameFormatNV12, true
case frame.FormatYUY2:
return C.AVBindFrameFormatYUY2, true
case frame.FormatUYVY:
return C.AVBindFrameFormatUYVY, true
default:
return 0, false
}
}
func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
switch f {
case C.AVBindFrameFormatI420:
return frame.FormatI420, true
case C.AVBindFrameFormatNV21:
return frame.FormatNV21, true
case C.AVBindFrameFormatNV12:
return frame.FormatNV12, true
case C.AVBindFrameFormatYUY2:
return frame.FormatYUY2, true
case C.AVBindFrameFormatUYVY:
return frame.FormatUYVY, true
default:
return "", false
}
}
// Devices uses AVFoundation to query a list of devices based on the media type
func Devices(mediaType MediaType) ([]Device, error) {
var cDevicesPtr C.PAVBindDevice
var cDevicesLen C.int
status := C.AVBindDevices(C.AVBindMediaType(mediaType), &cDevicesPtr, &cDevicesLen)
if status != nil {
return nil, fmt.Errorf("%s", C.GoString(status))
}
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
cDevices := (*[1 << 28]C.AVBindDevice)(unsafe.Pointer(cDevicesPtr))[:cDevicesLen:cDevicesLen]
devices := make([]Device, cDevicesLen)
for i := range devices {
devices[i].UID = C.GoString(&cDevices[i].uid[0])
devices[i].cDevice = cDevices[i]
}
return devices, nil
}
// 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()
cancelCtx context.Context
cancelFunc func()
closeWG sync.WaitGroup
lock sync.Mutex
}
func newReadCloser(onClose func()) *ReadCloser {
var rc 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
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:
// - For video, each call will return a frame.
// - For audio, each call will return a chunk which its size configured by Latency
func (rc *ReadCloser) Read() ([]byte, func(), error) {
data, ok := <-rc.dataChan
if !ok {
return nil, func() {}, io.EOF
}
return data, func() {}, nil
}
// 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()
}
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
func NewSession(device Device) (*Session, error) {
var session Session
status := C.AVBindSessionInit(device.cDevice, &session.cSession)
if status != nil {
return nil, fmt.Errorf("%s", C.GoString(status))
}
session.device = device
return &session, nil
}
// 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
}
status := C.AVBindSessionFree(&session.cSession)
if status != nil {
return fmt.Errorf("%s", C.GoString(status))
}
return nil
}
// Open start capturing session. As soon as it returns successfully, the data will start
// flowing. The raw data can be retrieved by using ReadCloser's Read method.
func (session *Session) Open(property prop.Media) (*ReadCloser, error) {
frameFormat, ok := frameFormatToAVBind(property.FrameFormat)
if !ok {
return nil, fmt.Errorf("Unsupported frame format")
}
cProperty := C.AVBindMediaProperty{
width: C.int(property.Width),
height: C.int(property.Height),
frameFormat: frameFormat,
}
rc := newReadCloser(func() {
C.AVBindSessionClose(session.cSession)
})
status := C.AVBindSessionOpen(
session.cSession,
cProperty,
C.AVBindDataCallback(unsafe.Pointer(C.onDataBridge)),
unsafe.Pointer(&rc.id),
)
if status != nil {
return nil, fmt.Errorf("%s", C.GoString(status))
}
return rc, nil
}
// Properties queries a list of properties that device supports
func (session *Session) Properties() []prop.Media {
var cPropertiesPtr C.PAVBindMediaProperty
var cPropertiesLen C.int
status := C.AVBindSessionProperties(session.cSession, &cPropertiesPtr, &cPropertiesLen)
if status != nil {
return nil
}
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
cProperties := (*[1 << 28]C.AVBindMediaProperty)(unsafe.Pointer(cPropertiesPtr))[:cPropertiesLen:cPropertiesLen]
var properties []prop.Media
for _, cProperty := range cProperties {
frameFormat, ok := frameFormatFromAVBind(cProperty.frameFormat)
if ok {
properties = append(properties, prop.Media{
Video: prop.Video{
Width: int(cProperty.width),
Height: int(cProperty.height),
FrameFormat: frameFormat,
},
})
}
}
return properties
}

View File

@@ -1,21 +1,101 @@
package codec package codec
import ( import (
"io" "time"
"github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop" "github.com/pion/mediadevices/pkg/prop"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v3"
) )
// RTPCodec wraps webrtc.RTPCodec. RTPCodec might extend webrtc.RTPCodec in the future.
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
func NewRTPH264Codec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
RTCPFeedback: nil,
},
PayloadType: 125,
},
Payloader: &codecs.H264Payloader{},
}
}
// NewRTPVP8Codec is a helper to create an VP8 codec
func NewRTPVP8Codec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
PayloadType: 96,
},
Payloader: &codecs.VP8Payloader{},
}
}
// NewRTPVP9Codec is a helper to create an VP9 codec
func NewRTPVP9Codec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP9,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
PayloadType: 98,
},
Payloader: &codecs.VP9Payloader{},
}
}
// NewRTPOpusCodec is a helper to create an Opus codec
func NewRTPOpusCodec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1",
RTCPFeedback: nil,
},
PayloadType: 111,
},
Payloader: &codecs.OpusPayloader{},
}
}
// AudioEncoderBuilder is the interface that wraps basic operations that are // AudioEncoderBuilder is the interface that wraps basic operations that are
// necessary to build the audio encoder. // necessary to build the audio encoder.
// //
// This interface is for codec implementors to provide codec specific params, // This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users. // but still giving generality for the users.
type AudioEncoderBuilder interface { type AudioEncoderBuilder interface {
// Name represents the codec name // RTPCodec represents the codec metadata
Name() string RTPCodec() *RTPCodec
// BuildAudioEncoder builds audio encoder by given media params and audio input // BuildAudioEncoder builds audio encoder by given media params and audio input
BuildAudioEncoder(r audio.Reader, p prop.Media) (ReadCloser, error) BuildAudioEncoder(r audio.Reader, p prop.Media) (ReadCloser, error)
} }
@@ -26,20 +106,43 @@ type AudioEncoderBuilder interface {
// This interface is for codec implementors to provide codec specific params, // This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users. // but still giving generality for the users.
type VideoEncoderBuilder interface { type VideoEncoderBuilder interface {
// Name represents the codec name // RTPCodec represents the codec metadata
Name() string RTPCodec() *RTPCodec
// BuildVideoEncoder builds video encoder by given media params and video input // BuildVideoEncoder builds video encoder by given media params and video input
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error) BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
} }
// ReadCloser is an io.ReadCloser with methods for rate limiting: SetBitRate and ForceKeyFrame // ReadCloser is an io.ReadCloser with a controller
type ReadCloser interface { type ReadCloser interface {
io.ReadCloser 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 // SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
// but this also means that the quality will also be lower. // but this also means that the quality will also be lower.
SetBitRate(int) error SetBitRate(int) error
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
} }
// BaseParams represents an codec's encoding properties // BaseParams represents an codec's encoding properties

View File

@@ -0,0 +1,121 @@
// 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)
}
}

196
pkg/codec/mmal/bridge.h Normal file
View File

@@ -0,0 +1,196 @@
#include <interface/mmal/mmal.h>
#include <interface/mmal/util/mmal_default_components.h>
#include <interface/mmal/util/mmal_util_params.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define CHK(__status, __msg) \
do { \
status.code = __status; \
if (status.code != MMAL_SUCCESS) { \
status.msg = __msg; \
goto CleanUp; \
} \
} while (0)
typedef struct Status {
MMAL_STATUS_T code;
const char *msg;
} Status;
typedef struct Slice {
uint8_t *data;
int len;
} Slice;
typedef struct Params {
int width, height;
uint32_t bitrate;
uint32_t key_frame_interval;
} Params;
typedef struct Encoder {
MMAL_COMPONENT_T *component;
MMAL_PORT_T *port_in, *port_out;
MMAL_QUEUE_T *queue_out;
MMAL_POOL_T *pool_in, *pool_out;
} Encoder;
Status enc_new(Params, Encoder *);
Status enc_encode(Encoder *, Slice y, Slice cb, Slice cr, MMAL_BUFFER_HEADER_T **);
Status enc_close(Encoder *);
static void encoder_in_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) { mmal_buffer_header_release(buffer); }
static void encoder_out_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) {
MMAL_QUEUE_T *queue = (MMAL_QUEUE_T *)port->userdata;
mmal_queue_put(queue, buffer);
}
Status enc_new(Params params, Encoder *encoder) {
Status status = {0};
bool created = false;
memset(encoder, 0, sizeof(Encoder));
CHK(mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, &encoder->component),
"Failed to create video encoder component");
created = true;
encoder->port_in = encoder->component->input[0];
encoder->port_in->format->type = MMAL_ES_TYPE_VIDEO;
encoder->port_in->format->encoding = MMAL_ENCODING_I420;
encoder->port_in->format->es->video.width = params.width;
encoder->port_in->format->es->video.height = params.height;
encoder->port_in->format->es->video.par.num = 1;
encoder->port_in->format->es->video.par.den = 1;
encoder->port_in->format->es->video.crop.x = 0;
encoder->port_in->format->es->video.crop.y = 0;
encoder->port_in->format->es->video.crop.width = params.width;
encoder->port_in->format->es->video.crop.height = params.height;
CHK(mmal_port_format_commit(encoder->port_in), "Failed to commit input port format");
encoder->port_out = encoder->component->output[0];
encoder->port_out->format->type = MMAL_ES_TYPE_VIDEO;
encoder->port_out->format->encoding = MMAL_ENCODING_H264;
encoder->port_out->format->bitrate = params.bitrate;
CHK(mmal_port_format_commit(encoder->port_out), "Failed to commit output port format");
MMAL_PARAMETER_VIDEO_PROFILE_T encoder_param_profile = {0};
encoder_param_profile.hdr.id = MMAL_PARAMETER_PROFILE;
encoder_param_profile.hdr.size = sizeof(encoder_param_profile);
encoder_param_profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_BASELINE;
encoder_param_profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_42;
CHK(mmal_port_parameter_set(encoder->port_out, &encoder_param_profile.hdr), "Failed to set encoder profile param");
CHK(mmal_port_parameter_set_uint32(encoder->port_out, MMAL_PARAMETER_INTRAPERIOD, params.key_frame_interval),
"Failed to set intra period param");
MMAL_PARAMETER_VIDEO_RATECONTROL_T encoder_param_rate_control = {0};
encoder_param_rate_control.hdr.id = MMAL_PARAMETER_RATECONTROL;
encoder_param_rate_control.hdr.size = sizeof(encoder_param_rate_control);
encoder_param_rate_control.control = MMAL_VIDEO_RATECONTROL_VARIABLE;
CHK(mmal_port_parameter_set(encoder->port_out, &encoder_param_rate_control.hdr), "Failed to set rate control param");
// Some decoders expect SPS/PPS headers to be added to every frame
CHK(mmal_port_parameter_set_boolean(encoder->port_out, MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER, MMAL_TRUE),
"Failed to set inline header param");
CHK(mmal_port_parameter_set_boolean(encoder->port_out, MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME, MMAL_TRUE),
"Failed to set headers with frame param");
/* FIXME: Somehow this flag is broken? When this flag is on, the encoder will get stuck.
// Since our use case is mainly for real time streaming, the encoder should optimized for low latency
CHK(mmal_port_parameter_set_boolean(encoder->port_out, MMAL_PARAMETER_VIDEO_ENCODE_H264_LOW_LATENCY, MMAL_TRUE),
"Failed to set low latency param");
*/
// Now we know the format of both ports and the requirements of the encoder, we can create
// our buffer headers and their associated memory buffers. We use the buffer pool API for this.
encoder->port_in->buffer_num = encoder->port_in->buffer_num_min;
// mmal calculates recommended size that's big enough to store all of the pixels
encoder->port_in->buffer_size = encoder->port_in->buffer_size_recommended;
encoder->pool_in = mmal_pool_create(encoder->port_in->buffer_num, encoder->port_in->buffer_size);
encoder->port_out->buffer_num = encoder->port_out->buffer_num_min;
encoder->port_out->buffer_size = encoder->port_out->buffer_size_recommended;
encoder->pool_out = mmal_pool_create(encoder->port_out->buffer_num, encoder->port_out->buffer_size);
// Create a queue to store our encoded video frames. The callback we will get when
// a frame has been encoded will put the frame into this queue.
encoder->queue_out = mmal_queue_create();
encoder->port_out->userdata = (void *)encoder->queue_out;
// Enable all the input port and the output port.
// The callback specified here is the function which will be called when the buffer header
// we sent to the component has been processed.
CHK(mmal_port_enable(encoder->port_in, encoder_in_cb), "Failed to enable input port");
CHK(mmal_port_enable(encoder->port_out, encoder_out_cb), "Failed to enable output port");
// Enable the component. Components will only process data when they are enabled.
CHK(mmal_component_enable(encoder->component), "Failed to enable component");
CleanUp:
if (status.code != MMAL_SUCCESS) {
if (created) {
enc_close(encoder);
}
}
return status;
}
// enc_encode encodes y, cb, cr. The encoded frame is going to be stored in encoded_buffer.
// IMPORTANT: the caller is responsible to release the ownership of encoded_buffer
Status enc_encode(Encoder *encoder, Slice y, Slice cb, Slice cr, MMAL_BUFFER_HEADER_T **encoded_buffer) {
Status status = {0};
MMAL_BUFFER_HEADER_T *buffer;
uint32_t required_size;
// buffer should always be available since the encoding process is blocking
buffer = mmal_queue_get(encoder->pool_in->queue);
assert(buffer != NULL);
// buffer->data should've been allocated with enough memory to contain a frame by pool_in
required_size = y.len + cb.len + cr.len;
assert(buffer->alloc_size >= required_size);
memcpy(buffer->data, y.data, y.len);
memcpy(buffer->data + y.len, cb.data, cb.len);
memcpy(buffer->data + y.len + cb.len, cr.data, cr.len);
buffer->length = required_size;
CHK(mmal_port_send_buffer(encoder->port_in, buffer), "Failed to send filled buffer to input port");
while (1) {
// Send empty buffers to the output port to allow the encoder to start
// producing frames as soon as it gets input data
while ((buffer = mmal_queue_get(encoder->pool_out->queue)) != NULL) {
CHK(mmal_port_send_buffer(encoder->port_out, buffer), "Failed to send empty buffers to output port");
}
while ((buffer = mmal_queue_wait(encoder->queue_out)) != NULL) {
if ((buffer->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END) != 0) {
*encoded_buffer = buffer;
goto CleanUp;
}
mmal_buffer_header_release(buffer);
}
}
CleanUp:
return status;
}
Status enc_close(Encoder *encoder) {
Status status = {0};
mmal_pool_destroy(encoder->pool_out);
mmal_pool_destroy(encoder->pool_in);
mmal_queue_destroy(encoder->queue_out);
mmal_component_destroy(encoder->component);
CleanUp:
return status;
}

109
pkg/codec/mmal/mmal.go Normal file
View File

@@ -0,0 +1,109 @@
// Package mmal implements a hardware accelerated H264 encoder for raspberry pi.
// This package requires libmmal headers and libraries to be built.
// Reference: https://github.com/raspberrypi/userland/tree/master/interface/mmal
package mmal
// #cgo CFLAGS: -I/opt/vc/include
// #cgo LDFLAGS: -L/opt/vc/lib -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host -lvcsm -lvcos
// #include "bridge.h"
import "C"
import (
"fmt"
"image"
"io"
"sync"
"unsafe"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type encoder struct {
engine C.Encoder
r video.Reader
mu sync.Mutex
closed bool
cntr int
}
func statusToErr(status *C.Status) error {
return fmt.Errorf("(status = %d) %s", int(status.code), C.GoString(status.msg))
}
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
if params.KeyFrameInterval == 0 {
params.KeyFrameInterval = 60
}
if params.BitRate == 0 {
params.BitRate = 300000
}
e := encoder{
r: video.ToI420(r),
}
status := C.enc_new(C.Params{
width: C.int(p.Width),
height: C.int(p.Height),
bitrate: C.uint(params.BitRate),
key_frame_interval: C.uint(params.KeyFrameInterval),
}, &e.engine)
if status.code != 0 {
return nil, statusToErr(&status)
}
return &e, nil
}
func (e *encoder) Read() ([]byte, func(), error) {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil, func() {}, io.EOF
}
img, _, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
imgReal := img.(*image.YCbCr)
var y, cb, cr C.Slice
y.data = (*C.uchar)(&imgReal.Y[0])
y.len = C.int(len(imgReal.Y))
cb.data = (*C.uchar)(&imgReal.Cb[0])
cb.len = C.int(len(imgReal.Cb))
cr.data = (*C.uchar)(&imgReal.Cr[0])
cr.len = C.int(len(imgReal.Cr))
var encodedBuffer *C.MMAL_BUFFER_HEADER_T
status := C.enc_encode(&e.engine, y, cb, cr, &encodedBuffer)
if status.code != 0 {
return nil, func() {}, statusToErr(&status)
}
// GoBytes copies the C array to Go slice. After this, it's safe to release the C array
encoded := C.GoBytes(unsafe.Pointer(encodedBuffer.data), C.int(encodedBuffer.length))
// Release the buffer so that mmal can reuse this memory
C.mmal_buffer_header_release(encodedBuffer)
return encoded, func() {}, err
}
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.enc_close(&e.engine)
return nil
}

View File

@@ -0,0 +1,65 @@
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,
},
})
})
}
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()
}
}

31
pkg/codec/mmal/params.go Normal file
View File

@@ -0,0 +1,31 @@
package mmal
import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
// Params stores libmmal specific encoding parameters.
type Params struct {
codec.BaseParams
}
// NewParams returns default mmal codec specific parameters.
func NewParams() (Params, error) {
return Params{
BaseParams: codec.BaseParams{
KeyFrameInterval: 60,
},
}, nil
}
// RTPCodec represents the codec metadata
func (p *Params) RTPCodec() *codec.RTPCodec {
return codec.NewRTPH264Codec(90000)
}
// BuildVideoEncoder builds mmal encoder with given params
func (p *Params) BuildVideoEncoder(r video.Reader, property prop.Media) (codec.ReadCloser, error) {
return newEncoder(r, property, *p)
}

1
pkg/codec/openh264/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src

View File

@@ -0,0 +1,23 @@
Copyright (c) 2013, Cisco Systems
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,47 @@
git_url := https://github.com/cisco/openh264.git
version := v2.1.1
src_root_dir := src
lib_dir := lib
include_dir := include/openh264
lib_prefix := libopenh264
src_dir := $(src_root_dir)/$(MEDIADEVICES_TARGET_PLATFORM)
output_path := $(lib_dir)/$(lib_prefix)-$(MEDIADEVICES_TARGET_PLATFORM).a
# OS and Arch mapping to OpenH264 parameters
ifeq (windows,$(MEDIADEVICES_TARGET_OS))
os := mingw_nt
else
os := $(MEDIADEVICES_TARGET_OS)
endif
ifneq (,$(findstring $(MEDIADEVICES_TARGET_ARCH),armv6 armv7 armv8))
arch := arm
else ifeq (x64,$(MEDIADEVICES_TARGET_ARCH))
arch := x86_64
else
arch := $(MEDIADEVICES_TARGET_ARCH)
endif
.PHONY: all
all: guard-MEDIADEVICES_TARGET_PLATFORM guard-MEDIADEVICES_TARGET_PLATFORM \
guard-MEDIADEVICES_TARGET_OS guard-MEDIADEVICES_TARGET_ARCH \
$(output_path) headers
headers: | $(src_dir) $(include_dir)
@cp $(src_dir)/codec/api/svc/*.h $(include_dir)
$(output_path): $(src_dir)/$(lib_prefix).a | $(lib_dir)
@cp $< $@
$(src_dir)/$(lib_prefix).a: | $(src_dir)
$(MEDIADEVICES_TOOLCHAIN_BIN) make --directory=$(src_dir) $(lib_prefix).a \
OS=$(os) ARCH=$(arch)
$(src_dir): | $(src_root_dir)
git clone --depth=1 --branch=$(version) $(git_url) $@
$(src_root_dir) $(lib_dir) $(include_dir):
@mkdir -p $@
guard-%:
@if [ -z ${$*} ]; then echo "$* is a required environment variable"; exit 1; fi

View File

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

View File

@@ -12,6 +12,8 @@ typedef struct Slice {
typedef struct Frame { typedef struct Frame {
void *y, *u, *v; void *y, *u, *v;
int ystride;
int cstride;
int height; int height;
int width; int width;
} Frame; } Frame;
@@ -20,6 +22,15 @@ typedef struct EncoderOptions {
int width, height; int width, height;
int target_bitrate; int target_bitrate;
float max_fps; float max_fps;
EUsageType usage_type;
RC_MODES rc_mode;
bool enable_frame_skip;
unsigned int max_nal_size;
unsigned int intra_period;
int multiple_thread_idc;
unsigned int slice_num;
SliceModeEnum slice_mode;
unsigned int slice_size_constraint;
} EncoderOptions; } EncoderOptions;
typedef struct Encoder { typedef struct Encoder {
@@ -27,6 +38,7 @@ typedef struct Encoder {
ISVCEncoder *engine; ISVCEncoder *engine;
unsigned char *buff; unsigned char *buff;
int buff_size; int buff_size;
int force_key_frame;
} Encoder; } Encoder;
Encoder *enc_new(const EncoderOptions params, int *eresult); Encoder *enc_new(const EncoderOptions params, int *eresult);

View File

@@ -167,8 +167,8 @@ typedef enum {
DECODER_OPTION_LEVEL, ///< get current AU level info,only is used in GetOption DECODER_OPTION_LEVEL, ///< get current AU level info,only is used in GetOption
DECODER_OPTION_STATISTICS_LOG_INTERVAL,///< set log output interval DECODER_OPTION_STATISTICS_LOG_INTERVAL,///< set log output interval
DECODER_OPTION_IS_REF_PIC, ///< feedback current frame is ref pic or not DECODER_OPTION_IS_REF_PIC, ///< feedback current frame is ref pic or not
DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER ///< number of frames remaining in decoder buffer when pictures are required to re-ordered into display-order. DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER, ///< number of frames remaining in decoder buffer when pictures are required to re-ordered into display-order.
DECODER_OPTION_NUM_OF_THREADS, ///< number of decoding threads. The maximum thread count is equal or less than lesser of (cpu core counts and 16).
} DECODER_OPTION; } DECODER_OPTION;
/** /**

View File

@@ -201,6 +201,7 @@ typedef struct TagBufferInfo {
union { union {
SSysMEMBuffer sSystemBuffer; ///< memory info for one picture SSysMEMBuffer sSystemBuffer; ///< memory info for one picture
} UsrData; ///< output buffer info } UsrData; ///< output buffer info
unsigned char* pDst[3]; //point to picture YUV data
} SBufferInfo; } SBufferInfo;

View File

@@ -0,0 +1,15 @@
//The current file is auto-generated by script: generate_codec_ver.sh
#ifndef CODEC_VER_H
#define CODEC_VER_H
#include "codec_app_def.h"
static const OpenH264Version g_stCodecVersion = {2, 1, 1, 2005};
static const char* const g_strCodecVer = "OpenH264 version:2.1.1.2005";
#define OPENH264_MAJOR (2)
#define OPENH264_MINOR (1)
#define OPENH264_REVISION (1)
#define OPENH264_RESERVED (2005)
#endif // CODEC_VER_H

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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