Compare commits

...

373 Commits

Author SHA1 Message Date
renovate[bot]
62ae95a16a fix(deps): update module github.com/pion/webrtc/v4 to v4.1.4
Generated by Renovate Bot
2025-09-15 02:47:35 +00:00
renovate[bot]
1406108fb2 fix(deps): update module github.com/stretchr/testify to v1.11.1
Generated by Renovate Bot
2025-09-14 22:46:53 -04:00
renovate[bot]
a2a211857c chore(deps): update actions/setup-go action to v6
Generated by Renovate Bot
2025-09-14 22:41:32 -04:00
philipch07
c0721738c4 Apply go modernize (#650) 2025-09-14 21:55:37 -04:00
Leo (Lei) Kang
6047a32ea0 [VPX] vpx dynamic encoding (#647)
* Add vp8 decoder and dynamic vp8 decoding

* Add QPController

* change parameters into const

* move decoder into another PR

* use explicit parameter name
2025-09-04 14:33:07 -07:00
Leo (Lei) Kang
60bf158757 [CODEC] Add encoder bitrate tracker (#646)
add encoder bitrate tracker
2025-09-03 15:55:37 -07:00
renovate[bot]
c4fd28c7df fix(deps): update github.com/kbinani/screenshot digest to 089614a (#640)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:33:19 +09:00
renovate[bot]
c79e16706b fix(deps): update module github.com/pion/logging to v0.2.4 (#639)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:31:48 +09:00
renovate[bot]
89420ae84d fix(deps): update module github.com/pion/rtp to v1.8.19 (#632)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:31:16 +09:00
renovate[bot]
4db71e5b52 fix(deps): update module github.com/pion/webrtc/v4 to v4.1.2 (#637)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 10:38:39 +08:00
Jackie Li
a45a5e50cd fix track.unbind panic (#634)
Fix #633

Here the signalCh could have been closed by another goroutine, we should
use returned signalCh from `track.removeActivePeerConnection()` to close
the channel.

Actually, I don't know why we need to close the signalCh, we're using it
to send over the doneCh, why ever close it?
2025-06-13 13:31:00 +08:00
renovate[bot]
d90220699e fix(deps): update module github.com/pion/webrtc/v4 to v4.1.1 (#629)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 15:20:59 +08:00
renovate[bot]
71deb52047 fix(deps): update module github.com/pion/interceptor to v0.1.39 (#628)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 15:12:30 +08:00
Jingyang Kang
84ccb15157 Align H265 payloadType in NewRTPH265Codec with RegisterDefaultCodecs (#631) 2025-06-01 23:37:30 +08:00
renovate[bot]
ec6a4b6925 fix(deps): update module github.com/pion/webrtc/v4 to v4.1.0 (#627)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 16:08:17 +08:00
renovate[bot]
551fb6afd8 fix(deps): update module github.com/pion/webrtc/v4 to v4.0.15 (#625)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 23:29:49 +08:00
Kyle
20e8c50735 Implement bitrate controller in vpx and h264 codecs (#467)
Add bitrate control to vpx and h264 encoders

Co-authored-by: Jingyang Kang <3drxkjy@gmail.com>
2025-04-11 12:05:01 +08:00
Jingyang Kang
7211d077ee Update social links (#620) 2025-04-07 01:07:14 +08:00
代码人生
cd5f8eb43a set bitrate for openh264 (#566)
* Added the set bitrate function for openh264

* add examples

* Format

---------

Co-authored-by: Jingyang Kang <3drxkjy@gmail.com>
2025-04-06 20:47:23 +08:00
renovate[bot]
2d7bdd4e24 fix(deps): update module github.com/pion/webrtc/v4 to v4.0.14 (#616)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-06 11:49:54 +08:00
Jingyang Kang
5cad3f1b41 Feat/add helper function for av1 and h265 codec (#611)
* bump deps version

* Add NewRTPAV1Codec

* fix comment

* Update SDPFmtpLine for RTPAV1Codec

* Add helper function for h265 as well
2025-03-10 07:25:25 -07:00
Jingyang Kang
9d5e9cb3ea Add EncoderController() codec.EncoderController in Track interface. (#614)
* Add EncoderController() codec.EncoderController in Track interface.

* Update test, add EncoderController in mockMediaStreamTrack.
2025-03-10 07:25:14 -07:00
Jingyang Kang
9a47a07eba Add EncoderController method to VideoTrack and AudioTrack. (#612)
* Add GetEncoderController method for VideoTrack and AudioTrack.

* Change the naming, remove unecessary private getter func.
2025-03-09 22:35:55 -07:00
renovate[bot]
4c70a5f686 fix(deps): update module github.com/pion/webrtc/v4 to v4.0.13 (#608)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 10:00:02 +09:00
renovate[bot]
36a03e823e fix(deps): update github.com/kbinani/screenshot digest to a3924b7 (#601)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 13:46:01 +09:00
renovate[bot]
24e3a722cf fix(deps): update module github.com/pion/webrtc/v4 to v4.0.9 (#598)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 13:44:26 +09:00
Yohan Totting
ce9b412d0e Fix the VPX encoder is not properly handling the frame timestamp (#606)
* Fix the VPX encoder is not properly handling the frame timestamp

* Apply suggestions from code review

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

---------

Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2025-02-14 12:37:53 +07:00
Atsushi Watanabe
8f916c5c67 Fix dependency installation on the latest actions runner (#607)
Explicitly install libx11-dev and libxext-dev
2025-02-13 11:26:11 +09:00
renovate[bot]
c10fb000db chore(deps): update codecov/codecov-action action to v5 (#591)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 14:33:18 +09:00
renovate[bot]
309b50a801 fix(deps): update module github.com/gen2brain/malgo to v0.11.23 (#590)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 14:30:10 +09:00
renovate[bot]
6f6c42a695 fix(deps): update module golang.org/x/image to v0.23.0 (#595)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 14:29:38 +09:00
Jingyang Kang
4027590fcb Migrate to pion/webrtc/v4 (#592) 2024-12-10 14:12:02 +09:00
renovate[bot]
5ac53a463c fix(deps): update github.com/kbinani/screenshot digest to a8a2c5d (#578)
* Update github.com/kbinani/screenshot digest to a8a2c5d
* Update CI Go versions
2024-10-23 11:25:12 +09:00
Atsushi Watanabe
f2a550d5e2 Update CI Go versions (#589) 2024-10-23 11:21:31 +09:00
Atsushi Watanabe
9ea1754cc7 Unuse self-hosted macOS runner (#588)
GitHub-hosted macos runner is M1 by default now.
2024-10-23 09:40:05 +09:00
Atsushi Watanabe
bc0b11e3bf Improve ARM Darwin CI stability (#586)
- Isolate local homebrew cache directory from the host environment
- Weekly refresh homebrew cache
2024-10-22 18:21:03 +09:00
renovate[bot]
ac66b130b9 fix(deps): update module github.com/pion/webrtc/v3 to v3.3.4 (#574)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 16:36:33 +09:00
renovate[bot]
cf1be6fd31 fix(deps): update module golang.org/x/image to v0.21.0 (#583)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 16:35:52 +09:00
renovate[bot]
6339a1890f fix(deps): update module golang.org/x/image to v0.19.0 (#580)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 09:18:16 +09:00
renovate[bot]
6a00ffcdf8 fix(deps): update module golang.org/x/image to v0.18.0 [security] (#577)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-27 17:14:35 +09:00
renovate[bot]
c6aa90a133 fix(deps): update module github.com/gen2brain/malgo to v0.11.22 (#570)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:42:03 +09:00
renovate[bot]
e3eae5d8db fix(deps): update module github.com/pion/webrtc/v3 to v3.2.40 (#564)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:25:12 +09:00
renovate[bot]
7020457f63 chore(deps): update codecov/codecov-action action to v4 (#554)
* chore(deps): update codecov/codecov-action action to v4

* Set CODECOV_TOKEN

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2024-05-15 11:16:00 +09:00
Sean Pollock
e3fef141d9 Bump webcam dependency (#571)
Bump webcam dep
2024-05-01 12:55:04 -04:00
Sean Pollock
8fb8d65764 Add webcam close to linux camera discover (#568)
Add webcam close to linux discover
2024-04-30 10:14:26 -04:00
qiulin
ae63fa65bf Remove Windows camera cpp inline function (#569)
Co-authored-by: Qiulin Li <liqiulin@outlook.com>
2024-04-29 09:54:45 -04:00
Eric Daniels
23d77e2bb2 update to blackjack/webcam to v0.6.0 (#567) 2024-04-26 15:37:33 -04:00
renovate[bot]
02d4e0e896 fix(deps): update module github.com/pion/webrtc/v3 to v3.2.29 (#559)
Generated by Renovate Bot

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* avoid copy when not necessary

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

* add test

* Update pkg/io/video/broadcast_test.go

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

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

* Upgrade CI go versions for tests

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

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

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

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

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

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

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

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

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

* Fix color depth transform
2022-04-05 13:11:28 +08:00
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
238 changed files with 14087 additions and 2801 deletions

View File

@@ -11,14 +11,18 @@ jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: [ '1.15', '1.14' ]
go:
- '1.21' # oldest version this package supports
- '1.22' # oldstable Go version
- '1.23' # stable Go version
name: Linux Go ${{ matrix.go }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
@@ -28,52 +32,54 @@ jobs:
libopus-dev \
libva-dev \
libvpx-dev \
libx264-dev
- name: go vet
run: go vet ./...
- name: go build
run: go build ./...
- name: go build without CGO
run: go build . pkg/...
env:
CGO_ENABLED: 0
- name: go test
run: go test ./... -v -race
- name: go test without CGO
run: go test . pkg/... -v
env:
CGO_ENABLED: 0
libx11-dev \
libx264-dev \
libxext-dev
- name: Run Test Suite
run: make test
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
build-darwin:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
go: [ '1.15', '1.14' ]
go:
- '1.22'
- '1.23'
runs-on: macos-latest
name: Darwin Go ${{ matrix.go }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
run: |
which brew
brew install \
pkg-config \
opus \
libvpx \
x264
- name: go vet
run: go vet ./...
- name: go build
run: go build ./...
- name: go build without CGO
run: go build . pkg/...
env:
CGO_ENABLED: 0
- name: go test
run: go test ./... -v -race
- name: go test without CGO
run: go test . pkg/... -v
env:
CGO_ENABLED: 0
- name: Run Test Suite
run: make test
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
check-licenses:
runs-on: ubuntu-latest
name: Check Licenses
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: stable
- name: Installing go-licenses
run: go install github.com/google/go-licenses@latest
- name: Checking licenses
run: go-licenses check ./...

View File

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

5
.gitignore vendored
View File

@@ -10,3 +10,8 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.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

127
Makefile
View File

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

261
README.md
View File

@@ -1,75 +1,232 @@
# 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://discord.gg/PngbdqpFbt"><img src="https://img.shields.io/badge/join-us%20on%20discord-gray.svg?longCache=true&logo=discord&colorB=brightblue" alt="join us on Discord"></a> <a href="https://bsky.app/profile/pion.ly"><img src="https://img.shields.io/badge/follow-us%20on%20bluesky-gray.svg?longCache=true&logo=bluesky&colorB=brightblue" alt="Follow us on Bluesky"></a> <a href="https://twitter.com/_pion?ref_src=twsrc%5Etfw"><img src="https://img.shields.io/twitter/url.svg?label=Follow%20%40_pion&style=social&url=https%3A%2F%2Ftwitter.com%2F_pion" alt="Twitter Widget"></a>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/mediadevices/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://pkg.go.dev/badge/github.com/pion/mediadevices.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<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
```bash
go get -u github.com/pion/mediadevices
```
| Interface | Linux | Mac | Windows |
### Usage
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 | ✔️ | ✖️ | ✖️ |
| Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | ✔️ | ✔️ |
### Camera
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:
| OS | Library/Interface |
| :-----: | :---------------------------------------------------------------------: |
| Linux | [Video4Linux](https://en.wikipedia.org/wiki/Video4Linux) |
| Mac | [AVFoundation](https://developer.apple.com/av-foundation/) |
| Windows | [DirectShow](https://docs.microsoft.com/en-us/windows/win32/directshow) |
```go
import (
...
_ "github.com/pion/mediadevices/pkg/driver/camera"
)
```
| 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/) | ✔️ | ✖️ | ✖️ |
### 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`:
### Microphone
```go
package main
| 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/) |
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
)
### Screen casting
func main() {
// configure codec specific parameters
x264Params, _ := x264.NewParams()
x264Params.Preset = x264.PresetMedium
x264Params.BitRate = 1_000_000 // 1mbps
| OS | Library/Interface |
| :-----: | :---------------------------------------------------------------------: |
| Linux | [X11](https://en.wikipedia.org/wiki/X_Window_System) |
| Mac | N/A |
| Windows | N/A |
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&x264Params),
)
mediaStream, _ := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {},
Codec: codecSelector, // let GetUsermedia know available codecs
})
}
```
## 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.
| Audio Codec | Library/Interface |
| :---------: | :------------------------------------------------------: |
| OPUS | [libopus](http://opus-codec.org/) |
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
| 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/) |
#### Video Codecs
## Usage
##### x264
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
[Wiki](https://github.com/pion/mediadevices/wiki)
* 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.
## Contributing
* 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
- [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
##### openh264
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
## Project Status
* 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
[![Stargazers over time](https://starchart.cc/pion/mediadevices.svg)](https://starchart.cc/pion/mediadevices)
##### vpx
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
## References
* 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).
- https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs
- https://tools.ietf.org/html/rfc7742
* 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`
* If trying to use `import _ github.com/pion/mediadevices/pkg/driver/screen` note that you will need to use `GetDisplayMedia` instead of `GetUserMedia`
#### Failed to find vpx/x264/mmal/opus codecs
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
If you see the following error message at compile time:
```bash
# pkg-config --cflags -- vpx
Package vpx was not found in the pkg-config search path.
Perhaps you should add the directory containing `vpx.pc'
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`.
### Roadmap
The library can be used with our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
### Community
Pion has an active community on [Discord](https://discord.gg/PngbdqpFbt).
Follow the [Pion Twitter](https://twitter.com/_pion) and the [Pion Bluesky](https://bsky.app/profile/pion.ly) 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
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
### License
MIT License - see [LICENSE](LICENSE) for full text

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/v4"
)
// 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
### 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:
#### 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
Congrats, you have used pion-MediaDevices! 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,119 +1,107 @@
package main
import (
"fmt"
"image"
"io/ioutil"
"log"
"time"
pigo "github.com/esimov/pigo/core"
"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/io/video"
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
func markFacesTransformer(r video.Reader) video.Reader {
return video.ReaderFunc(func() (img image.Image, err error) {
img, err = r.Read()
if err != nil {
return
}
const (
confidenceLevel = 5.0
)
img = markFaces(img)
return
})
var (
cascade []byte
classifier *pigo.Pigo
)
func must(err error) {
if err != nil {
panic(err)
}
}
func detectFace(frame *image.YCbCr) bool {
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() {
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)
// prepare face detector
var err error
cascade, err = ioutil.ReadFile("facefinder")
if err != nil {
panic(err)
log.Fatalf("Error reading the cascade file: %s", err)
}
p := pigo.NewPigo()
// 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()
// 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 {
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) {
c.FrameFormat = prop.FrameFormatExact(frame.FormatI420) // most of the encoder accepts I420
c.Enabled = true
c.FrameFormat = prop.FrameFormatOneOf{frame.FormatI420, frame.FormatYUY2}
c.Width = prop.Int(640)
c.Height = prop.Int(480)
c.VideoTransform = markFacesTransformer
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
})
if err != nil {
panic(err)
}
must(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)
// 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")
}
}
// Set the remote SessionDescription
err = peerConnection.SetRemoteDescription(offer)
if err != nil {
panic(err)
release()
}
// 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,9 +1,37 @@
module github.com/pion/mediadevices/examples
go 1.14
go 1.21
replace github.com/pion/mediadevices => ../
require (
github.com/esimov/pigo v1.4.6
github.com/pion/mediadevices v0.0.0
github.com/pion/webrtc/v4 v4.1.4
)
// Please don't commit require entries of examples.
// `git checkout master examples/go.mod` to revert this file.
require github.com/pion/mediadevices v0.0.0-00010101000000-000000000000
require (
github.com/blackjack/webcam v0.6.1 // indirect
github.com/gen2brain/malgo v0.11.23 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.7 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.21 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.15 // indirect
github.com/pion/srtp/v3 v3.0.7 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.1.1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/image v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
)
replace github.com/pion/mediadevices v0.0.0 => ../

67
examples/go.sum Normal file
View File

@@ -0,0 +1,67 @@
github.com/blackjack/webcam v0.6.1 h1:K0T6Q0zto23U99gNAa5q/hFoye6uGcKr2aE6hFoxVoE=
github.com/blackjack/webcam v0.6.1/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gen2brain/malgo v0.11.23 h1:3/VAI8DP9/Wyx1CUDNlUQJVdWUvGErhjHDqYcHVk9ME=
github.com/gen2brain/malgo v0.11.23/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
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.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1
examples/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))
}

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

@@ -0,0 +1 @@
openh264

View File

@@ -0,0 +1,22 @@
## Instructions
### Install required codecs
In this example, we'll be using openh264 as our video codec. Therefore, we need to make sure that these codecs are installed within our system.
Installation steps:
* [openh264](https://github.com/pion/mediadevices#openh264)
### Download archive examplee
```
git clone https://github.com/pion/mediadevices.git
```
### Run openh264 example
Run `cd mediadevices/examples/openh264 && go build && ./openh264 recorded.h264`
set bitrate ,first press `Ctrl+c` or send a SIGINT signal.
To stop recording,second press `Ctrl+c` or send a SIGINT signal.

96
examples/openh264/main.go Normal file
View File

@@ -0,0 +1,96 @@
package main
import (
"fmt"
"image"
"io"
"os"
"os/signal"
"syscall"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/openh264"
_ "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)
params, err := openh264.NewParams()
must(err)
params.BitRate = 1_000_000 // 1mbps
codecSelector := mediadevices.NewCodecSelector(
mediadevices.WithVideoEncoders(&params),
)
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(params.RTPCodec().MimeType)
must(err)
defer reader.Close()
out, err := os.Create(dest)
must(err)
fmt.Println("Recording... Press Ctrl+c to Set BitRate")
go func() {
_, err = io.Copy(out, reader)
}()
<-sigs
if control, ok := reader.(codec.Controllable); ok {
if ctrl, ok := control.Controller().(codec.KeyFrameController); ok {
fmt.Println("Force Key")
ctrl.ForceKeyFrame()
}
if ctrl, ok := control.Controller().(codec.BitRateController); ok {
fmt.Println("SetBitRate")
ctrl.SetBitRate(200_000)
}
}
fmt.Println("Recording... Press Ctrl+c to stop")
<-sigs
must(err)
fmt.Println("Your video has been recorded to", dest)
}

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,120 +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/mediadevices/pkg/prop"
"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 = prop.FrameFormat(frame.FormatYUY2)
c.Enabled = true
c.Width = prop.Int(640)
c.Height = prop.Int(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
a=recvonly
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

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

@@ -0,0 +1 @@
vnc

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

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

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

@@ -0,0 +1,127 @@
package main
import (
"fmt"
"github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/driver/vncdriver"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/webrtc/v4"
// 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,19 +5,18 @@ import (
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v4"
// This is required to use opus audio encoder
"github.com/pion/mediadevices/pkg/codec/opus"
// 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
// 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"
"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,
// you can always swap your adapters with our dummy adapters below.
@@ -27,10 +26,6 @@ import (
_ "github.com/pion/mediadevices/pkg/driver/microphone" // This is required to register microphone adapter
)
const (
videoCodecName = webrtc.VP8
)
func main() {
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
@@ -45,11 +40,24 @@ func main() {
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{}
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
x264Params, err := x264.NewParams()
if err != nil {
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)
if err != nil {
panic(err)
@@ -61,45 +69,28 @@ func main() {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
md := mediadevices.NewMediaDevices(peerConnection)
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}
},
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
c.Enabled = true
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
c.Width = prop.Int(640)
c.Height = prop.Int(480)
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
Audio: func(c *mediadevices.MediaTrackConstraints) {
},
Codec: codecSelector,
})
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)
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(t,
webrtc.RtpTransceiverInit{
_, err = peerConnection.AddTransceiverFromTrack(track,
webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
},
)
@@ -120,13 +111,23 @@ func main() {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
// Block forever
select {}
}

46
go.mod
View File

@@ -1,13 +1,43 @@
module github.com/pion/mediadevices
go 1.13
go 1.21
require (
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
github.com/jfreymuth/pulse v0.0.0-20200817093420-a82ccdb5e8aa
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4
github.com/pion/webrtc/v2 v2.2.24
github.com/satori/go.uuid v1.2.0
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
github.com/blackjack/webcam v0.6.1
github.com/gen2brain/malgo v0.11.23
github.com/google/uuid v1.6.0
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
github.com/pion/interceptor v0.1.40
github.com/pion/logging v0.2.4
github.com/pion/rtcp v1.2.15
github.com/pion/rtp v1.8.21
github.com/pion/webrtc/v4 v4.1.4
github.com/stretchr/testify v1.11.1
golang.org/x/image v0.23.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gen2brain/shm v0.1.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/jezek/xgb v1.1.1 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.7 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.15 // indirect
github.com/pion/srtp/v3 v3.0.7 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

197
go.sum
View File

@@ -1,139 +1,76 @@
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/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/blackjack/webcam v0.6.1 h1:K0T6Q0zto23U99gNAa5q/hFoye6uGcKr2aE6hFoxVoE=
github.com/blackjack/webcam v0.6.1/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jfreymuth/pulse v0.0.0-20200817093420-a82ccdb5e8aa h1:qUZIj5+D3UDgfshNe8Cz/9maOxe8ddt43qwQH9vEEC8=
github.com/jfreymuth/pulse v0.0.0-20200817093420-a82ccdb5e8aa/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
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/gen2brain/malgo v0.11.23 h1:3/VAI8DP9/Wyx1CUDNlUQJVdWUvGErhjHDqYcHVk9ME=
github.com/gen2brain/malgo v0.11.23/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/gen2brain/shm v0.1.0 h1:MwPeg+zJQXN0RM9o+HqaSFypNoNEcNpeoGp0BTSx2YY=
github.com/gen2brain/shm v0.1.0/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
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/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4 h1:2ydMA2KbxRkYmIw3R8Me8dn90bejxBR4MKYXJ5THK3I=
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4/go.mod h1:v9KQvlDYMuvlwniumBVMlrB0VHQvyTgxNvaXjPmTmps=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pion/datachannel v1.4.20 h1:+uYUrxbhGuEt+9En81Necda5ul8M2h7mMsvGWkYZ/yI=
github.com/pion/datachannel v1.4.20/go.mod h1:hsjWYdTW5fMmtM4hVIxUNYqViRPv2A6ixzkQFd82wSc=
github.com/pion/dtls/v2 v2.0.1 h1:ddE7+V0faYRbyh4uPsRZ2vLdRrjVZn+wmCfI7jlBfaA=
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
github.com/pion/dtls/v2 v2.0.2 h1:FHCHTiM182Y8e15aFTiORroiATUI16ryHiQh8AIOJ1E=
github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
github.com/pion/ice v0.7.18 h1:KbAWlzWRUdX9SmehBh3gYpIFsirjhSQsCw6K2MjYMK0=
github.com/pion/ice v0.7.18/go.mod h1:+Bvnm3nYC6Nnp7VV6glUkuOfToB/AtMRZpOU8ihuf4c=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
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.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA=
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
github.com/pion/sctp v1.7.9 h1:n+A37cTMU08xL3Oodkz39XjtPReQliKyk01q96mGB5M=
github.com/pion/sctp v1.7.9/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw=
github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
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.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk=
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pion/webrtc/v2 v2.2.24 h1:l7q/iO96tMTElxuE2XGdNhCzklGcd9aVZ00XufASp0g=
github.com/pion/webrtc/v2 v2.2.24/go.mod h1:U/m+nvG1t8gInf8PwiDyJEDcd7qfl+jmGQXqTX2zGvo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/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-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
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/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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 MiB

View File

@@ -2,12 +2,18 @@ package codec
import (
"io"
"runtime"
"sync"
"testing"
"time"
)
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()
const (
dataSize = 1000
@@ -54,6 +60,11 @@ func TestMeasureBitRateStatic(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()
const (
dataSize = 1000

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.
const (
VideoInput MediaDeviceType = iota
VideoInput MediaDeviceType = iota + 1
AudioInput
AudioOutput
)

View File

@@ -7,95 +7,26 @@ import (
"github.com/pion/mediadevices/pkg/driver"
"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")
// 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
// 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
func (m *mediaDevices) GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
trackers := make([]Tracker, 0)
func GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
trackers := make([]Track, 0)
cleanTrackers := func() {
for _, t := range trackers {
t.Stop()
t.Close()
}
}
var videoConstraints MediaTrackConstraints
if constraints.Video != nil {
constraints.Video(&videoConstraints)
}
if videoConstraints.Enabled {
tracker, err := m.selectScreen(videoConstraints)
tracker, err := selectScreen(videoConstraints, constraints.Codec)
if err != nil {
cleanTrackers()
return nil, err
@@ -116,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
// with tracks containing the requested types of media.
// 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
trackers := make([]Tracker, 0)
trackers := make([]Track, 0)
cleanTrackers := func() {
for _, t := range trackers {
t.Stop()
t.Close()
}
}
var videoConstraints, audioConstraints MediaTrackConstraints
if constraints.Video != nil {
constraints.Video(&videoConstraints)
}
if constraints.Audio != nil {
constraints.Audio(&audioConstraints)
}
if videoConstraints.Enabled {
tracker, err := m.selectVideo(videoConstraints)
tracker, err := selectVideo(videoConstraints, constraints.Codec)
if err != nil {
cleanTrackers()
return nil, err
@@ -145,8 +69,9 @@ func (m *mediaDevices) GetUserMedia(constraints MediaStreamConstraints) (MediaSt
trackers = append(trackers, tracker)
}
if audioConstraints.Enabled {
tracker, err := m.selectAudio(audioConstraints)
if constraints.Audio != nil {
constraints.Audio(&audioConstraints)
tracker, err := selectAudio(audioConstraints, constraints.Codec)
if err != nil {
cleanTrackers()
return nil, err
@@ -195,12 +120,15 @@ func queryDriverProperties(filter driver.FilterFn) map[driver.Driver][]prop.Medi
func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints) (driver.Driver, MediaTrackConstraints, error) {
var bestDriver driver.Driver
var bestProp prop.Media
var foundPropertiesLog []string
minFitnessDist := math.Inf(1)
foundPropertiesLog = append(foundPropertiesLog, "\n============ Found Properties ============")
driverProperties := queryDriverProperties(filter)
for d, props := range driverProperties {
priority := float64(d.Info().Priority)
for _, p := range props {
foundPropertiesLog = append(foundPropertiesLog, p.String())
fitnessDist, ok := constraints.MediaConstraints.FitnessDistance(p)
if !ok {
continue
@@ -214,33 +142,25 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
}
}
foundPropertiesLog = append(foundPropertiesLog, "=============== Constraints ==============")
foundPropertiesLog = append(foundPropertiesLog, constraints.String())
foundPropertiesLog = append(foundPropertiesLog, "================ Best Fit ================")
if bestDriver == nil {
var foundProperties []string
for _, props := range driverProperties {
for _, p := range props {
foundProperties = append(foundProperties, fmt.Sprint(&p))
}
}
err := fmt.Errorf(`%w:
============ Found Properties ============
%s
=============== Constraints ==============
%s
`, errNotFound, strings.Join(foundProperties, "\n\n"), &constraints)
return nil, MediaTrackConstraints{}, err
foundPropertiesLog = append(foundPropertiesLog, "Not found")
logger.Debug(strings.Join(foundPropertiesLog, "\n\n"))
return nil, MediaTrackConstraints{}, errNotFound
}
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
}
func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker, error) {
func selectAudio(constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
typeFilter := driver.FilterAudioRecorder()
d, c, err := selectBestDriver(typeFilter, constraints)
@@ -248,9 +168,9 @@ func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker,
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()
notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen))
filter := driver.FilterAnd(typeFilter, notScreenFilter)
@@ -260,10 +180,10 @@ func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker,
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()
screenFilter := driver.FilterDeviceType(driver.Screen)
filter := driver.FilterAnd(typeFilter, screenFilter)
@@ -273,10 +193,10 @@ func (m *mediaDevices) selectScreen(constraints MediaTrackConstraints) (Tracker,
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(
driver.FilterFn(func(driver.Driver) bool { return true }))
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,91 +1,43 @@
package mediadevices
import (
"errors"
"io"
"testing"
"time"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v2/pkg/media"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/driver"
_ "github.com/pion/mediadevices/pkg/driver/audiotest"
_ "github.com/pion/mediadevices/pkg/driver/videotest"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
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: {
{Type: webrtc.RTPCodecTypeVideo, Name: "MockVideo", PayloadType: 1},
},
webrtc.RTPCodecTypeAudio: {
{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{
Video: func(c *MediaTrackConstraints) {
c.Enabled = true
c.Width = prop.Int(640)
c.Height = prop.Int(480)
params := videoParams
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
},
Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
},
}
constraintsWrong := MediaStreamConstraints{
Video: func(c *MediaTrackConstraints) {
c.Enabled = true
c.Width = prop.Int(640)
c.Width = prop.IntExact(10000)
c.Height = prop.Int(480)
params := videoParams
params.BitRate = 0
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
},
Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
},
}
// GetUserMedia with broken parameters
ms, err := md.GetUserMedia(constraintsWrong)
ms, err := GetUserMedia(constraintsWrong)
if err == nil {
t.Fatal("Expected error, but got nil")
}
// GetUserMedia with correct parameters
ms, err = md.GetUserMedia(constraints)
ms, err = GetUserMedia(constraints)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@@ -103,11 +55,11 @@ func TestGetUserMedia(t *testing.T) {
time.Sleep(50 * time.Millisecond)
for _, track := range tracks {
track.Stop()
track.Close()
}
// Stop and retry GetUserMedia
ms, err = md.GetUserMedia(constraints)
ms, err = GetUserMedia(constraints)
if err != nil {
t.Fatalf("Failed to GetUserMedia after the previsous tracks stopped: %v", err)
}
@@ -124,104 +76,10 @@ func TestGetUserMedia(t *testing.T) {
}
time.Sleep(50 * time.Millisecond)
for _, track := range tracks {
track.Stop()
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 {
return nil
}
func (t *mockTrack) Codec() *webrtc.RTPCodec {
return t.codec
}
func (t *mockTrack) ID() string {
return t.id
}
func (t *mockTrack) Kind() webrtc.RTPCodecType {
return t.codec.Type
}
type mockParams struct {
codec.BaseParams
name string
}
func (params *mockParams) Name() string {
return params.name
}
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 (e *mockCodec) SetBitRate(b int) error {
return nil
}
func (e *mockCodec) ForceKeyFrame() error {
return nil
}
type mockVideoCodec struct {
mockCodec
r video.Reader
closed chan struct{}
}
func (m *mockVideoCodec) Read(b []byte) (int, error) {
if _, err := m.r.Read(); err != nil {
return 0, err
}
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 }
func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
filterFn := driver.FilterVideoRecorder()
drivers := driver.GetManager().Query(filterFn)
@@ -240,37 +98,145 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
t.Fatal("expect to get at least 1 property")
}
expectedProp := driver.Properties()[0]
// Since this is a continuous value, bestConstraints should be set with the value that user specified
expectedProp.FrameRate = 30.0
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: prop.VideoConstraints{
// By reducing the width from the driver by a tiny amount, this property should be chosen.
// At the same time, we'll be able to find out if the return constraints will be properly set
// to the best constraints.
Width: prop.Int(expectedProp.Width - 1),
Height: prop.Int(expectedProp.Width),
FrameFormat: prop.FrameFormat(expectedProp.FrameFormat),
FrameRate: prop.Float(30.0),
},
// By reducing the value from the driver by a tiny amount, this property should be chosen.
// At the same time, we'll be able to find out if the return constraints will be properly set
// to the best constraints.
cases := map[string]struct {
width, height int
frameFormat frame.Format
frameRate float32
}{
"DifferentWidth": {
width: expectedProp.Width - 1,
height: expectedProp.Height,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentHeight": {
width: expectedProp.Width,
height: expectedProp.Height - 1,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentFrameFormat": {
width: expectedProp.Width,
height: expectedProp.Height,
frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate,
},
}
bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
var vc prop.VideoConstraints
if c.frameRate >= 0 {
vc = prop.VideoConstraints{
Width: prop.Int(c.width),
Height: prop.Int(c.height),
FrameFormat: prop.FrameFormat(c.frameFormat),
FrameRate: prop.Float(c.frameRate),
}
} else {
// do not specify the framerate
vc = prop.VideoConstraints{
Width: prop.Int(c.width),
Height: prop.Int(c.height),
FrameFormat: prop.FrameFormat(c.frameFormat),
}
}
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: vc,
},
}
bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
if err != nil {
t.Fatal(err)
}
if driver != bestDriver {
t.Fatal("best driver is not expected")
}
s := bestConstraints.selectedMedia
if s.Width != expectedProp.Width ||
s.Height != expectedProp.Height ||
s.FrameFormat != expectedProp.FrameFormat ||
s.FrameRate != expectedProp.FrameRate {
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
}
})
}
}
func TestSelectBestDriverConstraintsNoFit(t *testing.T) {
filterFn := driver.FilterVideoRecorder()
drivers := driver.GetManager().Query(filterFn)
if len(drivers) == 0 {
t.Fatal("expect to get at least 1 driver")
}
driver := drivers[0]
err := driver.Open()
if err != nil {
t.Fatal(err)
t.Fatal("expect to open driver successfully")
}
defer driver.Close()
if len(driver.Properties()) == 0 {
t.Fatal("expect to get at least 1 property")
}
expectedProp := driver.Properties()[0]
cases := map[string]struct {
width, height int
frameFormat frame.Format
frameRate float32
}{
"DifferentWidth": {
width: expectedProp.Width - 1,
height: expectedProp.Height,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentHeight": {
width: expectedProp.Width,
height: expectedProp.Height - 1,
frameFormat: expectedProp.FrameFormat,
frameRate: expectedProp.FrameRate,
},
"DifferentFrameFormat": {
width: expectedProp.Width,
height: expectedProp.Height,
frameFormat: frame.FormatI420,
frameRate: expectedProp.FrameRate,
},
}
if driver != bestDriver {
t.Fatal("best driver is not expected")
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
wantConstraints := MediaTrackConstraints{
MediaConstraints: prop.MediaConstraints{
VideoConstraints: prop.VideoConstraints{
Width: prop.IntExact(c.width),
Height: prop.IntExact(c.height),
FrameFormat: prop.FrameFormatExact(c.frameFormat),
FrameRate: prop.FloatExact(c.frameRate),
},
},
}
s := bestConstraints.selectedMedia
if s.Width != expectedProp.Width ||
s.Height != expectedProp.Height ||
s.FrameFormat != expectedProp.FrameFormat ||
s.FrameRate != expectedProp.FrameRate {
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
_, _, err := selectBestDriver(filterFn, wantConstraints)
if err == nil {
t.Fatal("expect to not find a driver that fits the constraints")
}
})
}
}

View File

@@ -3,88 +3,86 @@ package mediadevices
import (
"sync"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v4"
)
// MediaStream is an interface that represents a collection of existing tracks.
type MediaStream interface {
// 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() []Tracker
GetVideoTracks() []Track
// 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(t Tracker)
AddTrack(t Track)
// RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
RemoveTrack(t Tracker)
RemoveTrack(t Track)
}
type mediaStream struct {
trackers map[string]Tracker
l sync.RWMutex
tracks map[Track]struct{}
l sync.RWMutex
}
const rtpCodecTypeDefault webrtc.RTPCodecType = 0
const trackTypeDefault webrtc.RTPCodecType = 0
// NewMediaStream creates a MediaStream interface that's defined in
// https://w3c.github.io/mediacapture-main/#dom-mediastream
func NewMediaStream(trackers ...Tracker) (MediaStream, error) {
m := mediaStream{trackers: make(map[string]Tracker)}
func NewMediaStream(tracks ...Track) (MediaStream, error) {
m := mediaStream{tracks: make(map[Track]struct{})}
for _, tracker := range trackers {
id := tracker.LocalTrack().ID()
if _, ok := m.trackers[id]; !ok {
m.trackers[id] = tracker
for _, track := range tracks {
if _, ok := m.tracks[track]; !ok {
m.tracks[track] = struct{}{}
}
}
return &m, nil
}
func (m *mediaStream) GetAudioTracks() []Tracker {
func (m *mediaStream) GetAudioTracks() []Track {
return m.queryTracks(webrtc.RTPCodecTypeAudio)
}
func (m *mediaStream) GetVideoTracks() []Tracker {
func (m *mediaStream) GetVideoTracks() []Track {
return m.queryTracks(webrtc.RTPCodecTypeVideo)
}
func (m *mediaStream) GetTracks() []Tracker {
return m.queryTracks(rtpCodecTypeDefault)
func (m *mediaStream) GetTracks() []Track {
return m.queryTracks(trackTypeDefault)
}
// 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.
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Tracker {
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Track {
m.l.RLock()
defer m.l.RUnlock()
result := make([]Tracker, 0)
for _, tracker := range m.trackers {
if tracker.LocalTrack().Kind() == t || t == rtpCodecTypeDefault {
result = append(result, tracker)
result := make([]Track, 0)
for track := range m.tracks {
if track.Kind() == t || t == trackTypeDefault {
result = append(result, track)
}
}
return result
}
func (m *mediaStream) AddTrack(t Tracker) {
func (m *mediaStream) AddTrack(t Track) {
m.l.Lock()
defer m.l.Unlock()
id := t.LocalTrack().ID()
if _, ok := m.trackers[id]; ok {
if _, ok := m.tracks[t]; ok {
return
}
m.trackers[id] = t
m.tracks[t] = struct{}{}
}
func (m *mediaStream) RemoveTrack(t Tracker) {
func (m *mediaStream) RemoveTrack(t Track) {
m.l.Lock()
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"
"slices"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/webrtc/v4"
)
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 (track *mockMediaStreamTrack) EncoderController() codec.EncoderController {
return 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 := slices.Contains(expected, a)
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,40 +1,18 @@
package mediadevices
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"
)
type MediaStreamConstraints struct {
Audio MediaOption
Video MediaOption
Codec *CodecSelector
}
// MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints
type MediaTrackConstraints struct {
prop.MediaConstraints
Enabled bool
// 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
selectedMedia prop.Media
}

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")
}
}

View File

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

View File

@@ -1,17 +1,17 @@
// MIT License
//
//
// Copyright (c) 2019-2020 Pion
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -46,6 +46,8 @@
} \
} while(0)
static NSString *const UnrecognizedMacOSVersionException = @"UnrecognizedMacOSVersionException";
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property (readonly) AVBindDataCallback mCallback;
@@ -74,32 +76,61 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
!CMSampleBufferIsValid(sampleBuffer) ||
!CMSampleBufferDataIsReady(sampleBuffer)) {
return;
return;
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer == NULL) {
return;
return;
}
imageBuffer = CVBufferRetain(imageBuffer);
CVBufferRetain(imageBuffer);
CVReturn ret =
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
if (ret != kCVReturnSuccess) {
return;
CVBufferRelease(imageBuffer);
return;
}
size_t heightY = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
size_t heightUV = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
int len = (int)((heightY * bytesPerRowY) + (2 * heightUV * bytesPerRowUV));
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
_mCallback(_mPUserData, buf, len);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
// Handle NV12 special case
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
// Get actual dimensions of image (without padding)
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t totalSize = /*Y plane*/ width * height + /*UV plane*/ width * height / 2;
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
void *mergedBuffer = malloc(totalSize);
if (!mergedBuffer) {
NSLog(@"Failed to allocate memory for merged buffer");
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
CVBufferRelease(imageBuffer);
return;
}
// Truncate data where we know it should end to strip padding
void *yPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
for (size_t row = 0; row < height; ++row) {
memcpy(mergedBuffer + row * width, yPlaneBuf + row * bytesPerRowY, width);
}
void *uvPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
for (size_t row = 0; row < height / 2; ++row) {
memcpy(mergedBuffer + width * height + row * width, uvPlaneBuf + row * bytesPerRowUV, width);
}
_mCallback(_mPUserData, mergedBuffer, (int)totalSize);
free(mergedBuffer);
} else {
void *buf = CVPixelBufferGetBaseAddress(imageBuffer);
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
_mCallback(_mPUserData, buf, (int)dataSize);
}
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
CVBufferRelease(imageBuffer);
}
@@ -133,13 +164,21 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
STATUS retStatus = STATUS_OK;
// Useful mapping reference from ffmpeg:
// https://github.com/FFmpeg/FFmpeg/blob/c810a9502cebe32e1dd08ee3d0d17053dde44aa9/libavdevice/avfoundation.m#L53-L80
switch (format) {
case AVBindFrameFormatNV21:
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
case AVBindFrameFormatI420:
*pFourCC = kCVPixelFormatType_420YpCbCr8Planar;
break;
case AVBindFrameFormatNV12:
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
break;
case AVBindFrameFormatUYVY:
*pFourCC = kCVPixelFormatType_422YpCbCr8;
break;
case AVBindFrameFormatYUYV:
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
@@ -150,15 +189,22 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
STATUS retStatus = STATUS_OK;
switch (fourCC) {
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
*pFormat = AVBindFrameFormatNV21;
break;
case kCVPixelFormatType_422YpCbCr8:
*pFormat = AVBindFrameFormatUYVY;
break;
case kCVPixelFormatType_420YpCbCr8Planar:
*pFormat = AVBindFrameFormatI420;
break;
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
*pFormat = AVBindFrameFormatNV12;
break;
case kCVPixelFormatType_422YpCbCr8:
*pFormat = AVBindFrameFormatUYVY;
break;
case kCVPixelFormatType_422YpCbCr8_yuvs:
*pFormat = AVBindFrameFormatYUYV;
break;
// TODO: Add the rest of frame formats
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
default:
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
}
return retStatus;
}
@@ -170,34 +216,53 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(mediaType == AVBindMediaTypeVideo || mediaType == AVBindMediaTypeAudio, STATUS_UNSUPPORTED_MEDIA_TYPE);
CHK(ppDevices != NULL && pLen != NULL, STATUS_NULL_ARG);
PAVBindDevice pDevice;
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
NSArray *refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeExternalUnknown
];
NSArray *refAllTypes;
#if defined(MAC_OS_VERSION_14_0)
if (@available(macOS 14.0, *)) {
refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeMicrophone,
AVCaptureDeviceTypeExternal,
];
} else {
@throw [NSException exceptionWithName:UnrecognizedMacOSVersionException
reason:@"Unrecognized or unsupported macOS version detected."
userInfo:nil];
}
#else
refAllTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeExternalUnknown,
];
#endif
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes: refAllTypes
mediaType: _mediaType
position: AVCaptureDevicePositionUnspecified];
discoverySessionWithDeviceTypes: refAllTypes
mediaType: _mediaType
position: AVCaptureDevicePositionUnspecified];
int i = 0;
for (AVCaptureDevice *refDevice in refSession.devices) {
if (i >= MAX_DEVICES) {
break;
}
pDevice = devices + i;
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
strncpy(pDevice->name, refDevice.localizedName.UTF8String, MAX_DEVICE_NAME_CHARS);
pDevice->name[MAX_DEVICE_NAME_CHARS] = '\0';
i++;
}
*ppDevices = devices;
*pLen = i;
cleanup:
[refPool drain];
return retStatus;
@@ -217,7 +282,7 @@ STATUS AVBindSessionInit(AVBindDevice device, PAVBindSession *ppSessionResult) {
pSession->device = device;
pSession->refCaptureSession = NULL;
*ppSessionResult = pSession;
cleanup:
return retStatus;
}
@@ -244,15 +309,15 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
STATUS retStatus = STATUS_OK;
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(pSession != NULL && dataCallback != NULL, STATUS_NULL_ARG);
AVCaptureDeviceInput *refInput;
NSError *refErr = NULL;
NSString *refUID = [NSString stringWithUTF8String: pSession->device.uid];
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refUID];
refInput = [[AVCaptureDeviceInput alloc] initWithDevice: refDevice error: &refErr];
CHK(refErr == NULL, STATUS_DEVICE_INIT_FAILED);
AVCaptureSession *refCaptureSession = [[AVCaptureSession alloc] init];
refCaptureSession.sessionPreset = AVCaptureSessionPresetMedium;
[refCaptureSession addInput: refInput];
@@ -261,7 +326,7 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
VideoDataDelegate *pDelegate = [[VideoDataDelegate alloc]
init: dataCallback
withUserData: pUserData];
AVCaptureVideoDataOutput *pOutput = [[AVCaptureVideoDataOutput alloc] init];
FourCharCode fourCC;
CHK_STATUS(frameFormatToFourCC(property.frameFormat, &fourCC));
@@ -279,10 +344,10 @@ STATUS AVBindSessionOpen(PAVBindSession pSession,
} else {
// TODO: implement audio pipeline
}
pSession->refCaptureSession = [refCaptureSession retain];
[refCaptureSession startRunning];
cleanup:
[refPool drain];
return retStatus;
@@ -293,20 +358,30 @@ STATUS AVBindSessionClose(PAVBindSession pSession) {
STATUS retStatus = STATUS_OK;
CHK(pSession != NULL, STATUS_NULL_ARG);
CHK(pSession->refCaptureSession != NULL, STATUS_OK);
[pSession->refCaptureSession stopRunning];
[pSession->refCaptureSession release];
pSession->refCaptureSession = NULL;
cleanup:
return retStatus;
}
static NSString* FourCCString(FourCharCode code) {
NSString *result = [NSString stringWithFormat:@"%c%c%c%c",
(code >> 24) & 0xff,
(code >> 16) & 0xff,
(code >> 8) & 0xff,
code & 0xff];
NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet];
return [result stringByTrimmingCharactersInSet:characterSet];
}
STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *ppProperties, int *pLen) {
STATUS retStatus = STATUS_OK;
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
CHK(pSession != NULL && ppProperties != NULL && pLen != NULL, STATUS_NULL_ARG);
NSString *refDeviceUID = [NSString stringWithUTF8String: pSession->device.uid];
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refDeviceUID];
FourCharCode fourCC;
@@ -319,15 +394,17 @@ STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *pp
for (AVCaptureDeviceFormat *refFormat in refDevice.formats) {
// TODO: Probably gives a warn to the user
if (len >= MAX_PROPERTIES) {
NSLog(@"[WARNING] skipping the rest of properties due to MAX_PROPERTIES");
break;
}
if ([refFormat.mediaType isEqual:AVMediaTypeVideo]) {
fourCC = CMFormatDescriptionGetMediaSubType(refFormat.formatDescription);
if (frameFormatFromFourCC(fourCC, &pProperty->frameFormat) != STATUS_OK) {
NSLog(@"[WARNING] skipping %@ %dx%d since it's not supported", FourCCString(fourCC), videoDimensions.width, videoDimensions.height);
continue;
}
videoFormat = (CMVideoFormatDescriptionRef) refFormat.formatDescription;
videoDimensions = CMVideoFormatDescriptionGetDimensions(videoFormat);
pProperty->height = videoDimensions.height;
@@ -335,16 +412,16 @@ STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *pp
} else {
// TODO: Get audio properties
}
pProperty++;
len++;
}
*ppProperties = pSession->properties;
*pLen = len;
cleanup:
[refPool drain];
return retStatus;
}

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
package codec
import (
"time"
)
type BitrateTracker struct {
windowSize time.Duration
buffer []int
times []time.Time
}
func NewBitrateTracker(windowSize time.Duration) *BitrateTracker {
return &BitrateTracker{
windowSize: windowSize,
}
}
func (bt *BitrateTracker) AddFrame(sizeBytes int, timestamp time.Time) {
bt.buffer = append(bt.buffer, sizeBytes)
bt.times = append(bt.times, timestamp)
// Remove old entries outside the window
cutoff := timestamp.Add(-bt.windowSize)
i := 0
for ; i < len(bt.times); i++ {
if bt.times[i].After(cutoff) {
break
}
}
bt.buffer = bt.buffer[i:]
bt.times = bt.times[i:]
}
func (bt *BitrateTracker) GetBitrate() float64 {
if len(bt.times) < 2 {
return 0
}
totalBytes := 0
for _, b := range bt.buffer {
totalBytes += b
}
duration := bt.times[len(bt.times)-1].Sub(bt.times[0]).Seconds()
if duration <= 0 {
return 0
}
return float64(totalBytes*8) / duration // bits per second
}

View File

@@ -0,0 +1,19 @@
package codec
import (
"math"
"testing"
"time"
)
func TestBitrateTracker(t *testing.T) {
packetSize := 1000
bt := NewBitrateTracker(time.Second)
bt.AddFrame(packetSize, time.Now())
bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*100))
bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*999))
eps := float64(packetSize*8) / 10
if got, want := bt.GetBitrate(), float64(packetSize*8)*3; math.Abs(got-want) > eps {
t.Fatalf("GetBitrate() = %v, want %v (|diff| <= %v)", got, want, eps)
}
}

View File

@@ -1,21 +1,135 @@
package codec
import (
"io"
"time"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v4"
)
// 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{},
}
}
// NewRTPH265Codec is a helper to create an H265 codec
func NewRTPH265Codec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
PayloadType: 116,
},
Payloader: &codecs.H265Payloader{},
}
}
// 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{},
}
}
// NewRTPAV1Codec is a helper to create an AV1 codec
func NewRTPAV1Codec(clockrate uint32) *RTPCodec {
return &RTPCodec{
RTPCodecParameters: webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeAV1,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "level-idx=5;profile=0;tier=0",
RTCPFeedback: nil,
},
PayloadType: 99,
},
Payloader: &codecs.AV1Payloader{},
}
}
// 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
// necessary to build the audio encoder.
//
// This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users.
type AudioEncoderBuilder interface {
// Name represents the codec name
Name() string
// RTPCodec represents the codec metadata
RTPCodec() *RTPCodec
// BuildAudioEncoder builds audio encoder by given media params and audio input
BuildAudioEncoder(r audio.Reader, p prop.Media) (ReadCloser, error)
}
@@ -26,20 +140,49 @@ type AudioEncoderBuilder interface {
// This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users.
type VideoEncoderBuilder interface {
// Name represents the codec name
Name() string
// RTPCodec represents the codec metadata
RTPCodec() *RTPCodec
// BuildVideoEncoder builds video encoder by given media params and video input
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 {
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 any
// Controllable is a interface representing a encoder which can be controlled
// after it's initialisation with an EncoderController
type Controllable interface {
Controller() EncoderController
}
// KeyFrameController is a interface representing an encoder that can be forced to produce key frame on demand
type KeyFrameController interface {
EncoderController
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
// BitRateController is a interface representing an encoder which can have a variable bit rate
type BitRateController interface {
EncoderController
// SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
// but this also means that the quality will also be lower.
SetBitRate(int) error
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
type QPController interface {
EncoderController
// DynamicQPControl adjusts the QP of the encoder based on the current and target bitrate
DynamicQPControl(currentBitrate int, targetBitrate int) error
}
// BaseParams represents an codec's encoding properties

View File

@@ -0,0 +1,159 @@
// Package codectest provides shared test for codec implementations.
package codectest
import (
"image"
"io"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
func assertNoPanic(t *testing.T, fn func() error, msg string) error {
defer func() {
if r := recover(); r != nil {
t.Errorf("panic: %v: %s", r, msg)
}
}()
return fn()
}
func AudioEncoderSimpleReadTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
var eof bool
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
if eof {
return nil, nil, io.EOF
}
return w, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Fatal("Encoded frame is empty")
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func VideoEncoderSimpleReadTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
var eof bool
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
if eof {
return nil, nil, io.EOF
}
return img, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Errorf("Encoded frame is empty (%d)", i)
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func AudioEncoderCloseTwiceTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media) {
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
t.Fatal(err)
}
}
func VideoEncoderCloseTwiceTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media) {
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
t.Fatal(err)
}
}
func AudioEncoderReadAfterCloseTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
return w, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, func() error {
_, _, err := enc.Read()
return err
}, "on Read()"); err != io.EOF {
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
}
}
func VideoEncoderReadAfterCloseTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return img, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, func() error {
_, _, err := enc.Read()
return err
}, "on Read()"); err != io.EOF {
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
}
}

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

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

@@ -0,0 +1,110 @@
// 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, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
imgReal := img.(*image.YCbCr)
var y, cb, cr C.Slice
y.data = (*C.uchar)(&imgReal.Y[0])
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,84 @@
package mmal
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderReadAfterCloseTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

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;
}
// TODO: Remove hardcoded values
params.iUsageType = CAMERA_VIDEO_REAL_TIME;
params.iUsageType = opts.usage_type;
params.iPicWidth = opts.width;
params.iPicHeight = opts.height;
params.iTargetBitrate = opts.target_bitrate;
params.iMaxBitrate = opts.target_bitrate;
params.iRCMode = RC_BITRATE_MODE;
params.iRCMode = opts.rc_mode;
params.fMaxFrameRate = opts.max_fps;
params.bEnableFrameSkip = true;
params.uiMaxNalSize = 0;
params.uiIntraPeriod = 30;
// set to 0, so that it'll automatically use multi threads when needed
params.iMultipleThreadIdc = 0;
params.bEnableFrameSkip = opts.enable_frame_skip;
params.uiMaxNalSize = opts.max_nal_size;
params.uiIntraPeriod = opts.intra_period;
params.iMultipleThreadIdc = opts.multiple_thread_idc;
// The base spatial layer 0 is the only one we use.
params.sSpatialLayers[0].iVideoWidth = params.iPicWidth;
params.sSpatialLayers[0].iVideoHeight = params.iPicHeight;
params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate;
params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate;
params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate;
// Single NAL unit mode
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SIZELIMITED_SLICE;
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = 12800;
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = opts.slice_num;
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = opts.slice_mode;
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = opts.slice_size_constraint;
rv = engine->InitializeExt(&params);
if (rv != 0) {
@@ -72,6 +69,16 @@ void enc_free(Encoder *e, int *eresult) {
free(e);
}
void enc_set_bitrate(Encoder *e, int bitrate) {
SEncParamExt encParamExt;
e->engine->GetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &encParamExt);
encParamExt.iTargetBitrate=bitrate;
encParamExt.iMaxBitrate=bitrate;
encParamExt.sSpatialLayers[0].iSpatialBitrate = bitrate;
encParamExt.sSpatialLayers[0].iMaxSpatialBitrate = bitrate;
e->engine->SetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &encParamExt);
}
// There's a good reference from ffmpeg in using the encode_frame
// Reference: https://ffmpeg.org/doxygen/2.6/libopenh264enc_8c_source.html
Slice enc_encode(Encoder *e, Frame f, int *eresult) {
@@ -80,12 +87,16 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
SFrameBSInfo info = {0};
Slice payload = {0};
if(e->force_key_frame == 1) {
e->engine->ForceIntraFrame(true);
e->force_key_frame = 0;
}
pic.iPicWidth = f.width;
pic.iPicHeight = f.height;
pic.iColorFormat = videoFormatI420;
// We always received I420 format
pic.iStride[0] = pic.iPicWidth;
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
pic.iStride[0] = f.ystride;
pic.iStride[1] = pic.iStride[2] = f.cstride;
pic.pData[0] = (unsigned char *)f.y;
pic.pData[1] = (unsigned char *)f.u;
pic.pData[2] = (unsigned char *)f.v;

View File

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

View File

@@ -167,8 +167,8 @@ typedef enum {
DECODER_OPTION_LEVEL, ///< get current AU level info,only is used in GetOption
DECODER_OPTION_STATISTICS_LOG_INTERVAL,///< set log output interval
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;
/**

View File

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

Binary file not shown.

Binary file not shown.

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