mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 21:02:17 +08:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
72c1d7bb89 | ||
![]() |
fc301a8a92 | ||
![]() |
aca3ee9126 | ||
![]() |
4924411e88 | ||
![]() |
b88f541c4f | ||
![]() |
09f6bdeac4 | ||
![]() |
e64f0d8697 | ||
![]() |
36f908c6e2 | ||
![]() |
9987e01d3f | ||
![]() |
ae173b1b61 | ||
![]() |
4eea55285e | ||
![]() |
18cf1fe38a | ||
![]() |
138499b52d | ||
![]() |
87486146d5 | ||
![]() |
cadb155755 | ||
![]() |
2372f55064 | ||
![]() |
8568b1b20d | ||
![]() |
f0f6be7350 | ||
![]() |
8146e84f2f | ||
![]() |
4ff24bd656 | ||
![]() |
64dbe507f0 | ||
![]() |
dffaf0fcb4 | ||
![]() |
d3adaeea1a | ||
![]() |
7a414948c6 | ||
![]() |
e1616b8cc2 | ||
![]() |
52a080b55a | ||
![]() |
c2fe66c579 | ||
![]() |
ac50077e77 | ||
![]() |
2f5c61e1f3 | ||
![]() |
0715258726 | ||
![]() |
bccff100e5 | ||
![]() |
0507093a59 | ||
![]() |
bf290b026c | ||
![]() |
0dc4f43c94 | ||
![]() |
14bfaa5dbd | ||
![]() |
09c31a264c | ||
![]() |
30badd819d | ||
![]() |
dc8aeea11f | ||
![]() |
57c9ba0fc5 | ||
![]() |
b9ce5bb861 | ||
![]() |
e4ac96ea6b | ||
![]() |
11bf55f80c | ||
![]() |
55881ddd41 | ||
![]() |
62009a882b | ||
![]() |
dbd37689e4 | ||
![]() |
d561715bf9 | ||
![]() |
76ba048312 | ||
![]() |
5da0ebf443 |
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -52,17 +52,30 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
# Set up local brew only on self-hosted darwin
|
||||||
- name: Checkout Homebrew
|
- name: Checkout Homebrew
|
||||||
if: matrix.runs-on != 'macos-latest'
|
if: matrix.runs-on != 'macos-latest'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: Homebrew/brew
|
repository: Homebrew/brew
|
||||||
path: homebrew
|
path: homebrew
|
||||||
|
- name: Local brew cache key
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
id: brew-cache-key
|
||||||
|
run: echo "key=$(date +'%Y-%U')" | tee ${GITHUB_OUTPUT} # weekly update cache
|
||||||
|
- name: Cache local brew taps
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: homebrew/Library/Taps
|
||||||
|
key: ${{ runner.os }}-brew-taps-${{ steps.brew-cache-key.outputs.key }}
|
||||||
|
restore-keys: ${{ runner.os }}-brew-taps-
|
||||||
- name: Set up brew to install deps under temporary dir
|
- name: Set up brew to install deps under temporary dir
|
||||||
if: matrix.runs-on != 'macos-latest' # set up local brew only on self-hosted
|
if: matrix.runs-on != 'macos-latest'
|
||||||
run: |
|
run: |
|
||||||
dir="${GITHUB_WORKSPACE}/homebrew"
|
dir="${GITHUB_WORKSPACE}/homebrew"
|
||||||
cd "${dir}"
|
cd "${dir}"
|
||||||
@@ -78,6 +91,7 @@ jobs:
|
|||||||
echo "Brew update" >&2
|
echo "Brew update" >&2
|
||||||
brew update --force --quiet
|
brew update --force --quiet
|
||||||
chmod -R go-w "$(brew --prefix)/share/zsh"
|
chmod -R go-w "$(brew --prefix)/share/zsh"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
which brew
|
which brew
|
||||||
@@ -95,7 +109,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.19'
|
go-version: '1.19'
|
||||||
- name: Installing go-licenses
|
- name: Installing go-licenses
|
||||||
|
75
README.md
75
README.md
@@ -6,8 +6,8 @@
|
|||||||
<h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4>
|
<h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||||
<a href="https://github.com/pion/mediadevices/actions"><img src="https://github.com/pion/mediadevices/workflows/CI/badge.svg?branch=master" alt="Build status"></a>
|
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/mediadevices/test.yaml">
|
||||||
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://godoc.org/github.com/pion/mediadevices?status.svg" alt="GoDoc"></a>
|
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://pkg.go.dev/badge/github.com/pion/mediadevices.svg" alt="Go Reference"></a>
|
||||||
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||||
</p>
|
</p>
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
|
|
||||||
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
|
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
|
||||||
|
|
||||||
## Install
|
### Install
|
||||||
|
|
||||||
`go get -u github.com/pion/mediadevices`
|
```bash
|
||||||
|
go get -u github.com/pion/mediadevices
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
### Usage
|
||||||
|
|
||||||
The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
|
The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
|
||||||
|
|
||||||
@@ -67,25 +69,21 @@ func main() {
|
|||||||
output, _ := os.Create("frame.jpg")
|
output, _ := os.Create("frame.jpg")
|
||||||
jpeg.Encode(output, frame, nil)
|
jpeg.Encode(output, frame, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## More Examples
|
### More Examples
|
||||||
|
|
||||||
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
|
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
|
||||||
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
|
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
|
||||||
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
|
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
|
||||||
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
|
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
|
||||||
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
|
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
|
||||||
|
|
||||||
## Available Media Inputs
|
### Available Media Inputs
|
||||||
|
| Input | Linux | Mac | Windows |
|
||||||
| Input | Linux | Mac | Windows |
|
|
||||||
| :--------: | :---: | :-: | :-----: |
|
| :--------: | :---: | :-: | :-----: |
|
||||||
| Camera | ✔️ | ✔️ | ✔️ |
|
| Camera | ✔️ | ✔️ | ✔️ |
|
||||||
| Microphone | ✔️ | ✔️ | ✔️ |
|
| Microphone | ✔️ | ✔️ | ✔️ |
|
||||||
| Screen | ✔️ | ✔️ | ✔️ |
|
| Screen | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
|
By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
|
||||||
|
|
||||||
@@ -96,8 +94,7 @@ import (
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Codecs
|
### Available Codecs
|
||||||
|
|
||||||
In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`:
|
In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -130,9 +127,9 @@ Since `mediadevices` doesn't implement the video/audio codecs, it needs to call
|
|||||||
|
|
||||||
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
|
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
|
||||||
|
|
||||||
### Video Codecs
|
#### Video Codecs
|
||||||
|
|
||||||
#### x264
|
##### x264
|
||||||
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
|
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
|
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
|
||||||
@@ -140,19 +137,19 @@ A free software library and application for encoding video streams into the H.26
|
|||||||
* Mac: `brew install x264`
|
* Mac: `brew install x264`
|
||||||
* Ubuntu: `apt install libx264-dev`
|
* Ubuntu: `apt install libx264-dev`
|
||||||
|
|
||||||
#### mmal
|
##### mmal
|
||||||
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
|
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
|
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
|
||||||
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
|
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
|
||||||
|
|
||||||
#### openh264
|
##### openh264
|
||||||
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
|
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
|
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
|
||||||
* Installation: no installation needed, included as a static binary
|
* Installation: no installation needed, included as a static binary
|
||||||
|
|
||||||
#### vpx
|
##### vpx
|
||||||
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
|
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
|
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
|
||||||
@@ -160,7 +157,7 @@ A free software video codec library from Google and the Alliance for Open Media
|
|||||||
* Mac: `brew install libvpx`
|
* Mac: `brew install libvpx`
|
||||||
* Ubuntu: `apt install libvpx-dev`
|
* Ubuntu: `apt install libvpx-dev`
|
||||||
|
|
||||||
#### vaapi
|
##### vaapi
|
||||||
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
|
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
|
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
|
||||||
@@ -168,9 +165,9 @@ An open source API that allows applications such as VLC media player or GStreame
|
|||||||
* Ubuntu: `apt install libva-dev`
|
* Ubuntu: `apt install libva-dev`
|
||||||
|
|
||||||
|
|
||||||
### Audio Codecs
|
#### Audio Codecs
|
||||||
|
|
||||||
#### opus
|
##### opus
|
||||||
A totally open, royalty-free, highly versatile audio codec.
|
A totally open, royalty-free, highly versatile audio codec.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
|
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
|
||||||
@@ -178,16 +175,14 @@ A totally open, royalty-free, highly versatile audio codec.
|
|||||||
* Mac: `brew install opus`
|
* Mac: `brew install opus`
|
||||||
* Ubuntu: `apt install libopus-dev`
|
* Ubuntu: `apt install libopus-dev`
|
||||||
|
|
||||||
## Benchmark
|
### Benchmark
|
||||||
|
|
||||||
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
|
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
|
||||||
|
|
||||||
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
|
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
|
||||||
|
|
||||||
## FAQ
|
### FAQ
|
||||||
|
|
||||||
### Failed to find the best driver that fits the constraints
|
|
||||||
|
|
||||||
|
#### Failed to find the best driver that fits the constraints
|
||||||
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
|
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
|
||||||
|
|
||||||
1. Open all registered drivers
|
1. Open all registered drivers
|
||||||
@@ -198,13 +193,14 @@ So, when `mediadevices` returns `failed to find the best driver that fits the co
|
|||||||
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
|
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
|
||||||
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
|
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
|
||||||
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
|
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
|
||||||
|
* If trying to use `import _ github.com/pion/mediadevices/pkg/driver/screen` note that you will need to use `GetDisplayMedia` instead of `GetUserMedia`
|
||||||
|
|
||||||
### Failed to find vpx/x264/mmal/opus codecs
|
#### Failed to find vpx/x264/mmal/opus codecs
|
||||||
|
|
||||||
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
|
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
|
||||||
|
|
||||||
If you see the following error message at compile time:
|
If you see the following error message at compile time:
|
||||||
```
|
|
||||||
|
```bash
|
||||||
# pkg-config --cflags -- vpx
|
# pkg-config --cflags -- vpx
|
||||||
Package vpx was not found in the pkg-config search path.
|
Package vpx was not found in the pkg-config search path.
|
||||||
Perhaps you should add the directory containing `vpx.pc'
|
Perhaps you should add the directory containing `vpx.pc'
|
||||||
@@ -218,8 +214,10 @@ There are 2 common problems:
|
|||||||
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
|
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
|
||||||
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
|
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
|
||||||
|
|
||||||
|
### Roadmap
|
||||||
|
The library can be used with our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||||
|
|
||||||
## Community
|
### Community
|
||||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||||
|
|
||||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||||
@@ -227,11 +225,8 @@ Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and imp
|
|||||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||||
|
|
||||||
## Contributing
|
### Contributing
|
||||||
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
|
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
|
||||||
|
|
||||||
* [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
|
### License
|
||||||
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
|
|
||||||
|
|
||||||
## License
|
|
||||||
MIT License - see [LICENSE](LICENSE) for full text
|
MIT License - see [LICENSE](LICENSE) for full text
|
||||||
|
@@ -5,7 +5,7 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/esimov/pigo v1.4.6
|
github.com/esimov/pigo v1.4.6
|
||||||
github.com/pion/mediadevices v0.0.0
|
github.com/pion/mediadevices v0.0.0
|
||||||
github.com/pion/webrtc/v3 v3.1.50
|
github.com/pion/webrtc/v3 v3.2.11
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pion/mediadevices v0.0.0 => ../
|
replace github.com/pion/mediadevices v0.0.0 => ../
|
||||||
|
109
examples/go.sum
109
examples/go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
|
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc h1:7cMZ/f4xwkD3FUOcThPAm0uecSP5kSTUU/3RWsrmcww=
|
||||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -50,43 +50,43 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/ice/v2 v2.2.12 h1:n3M3lUMKQM5IoofhJo73D3qVla+mJN2nVvbSPq32Nig=
|
github.com/pion/ice/v2 v2.3.8 h1:/4vM7uFPJez3PhNhlqUcJhboYaDNWo+R8oAuMj2cKsA=
|
||||||
github.com/pion/ice/v2 v2.2.12/go.mod h1:z2KXVFyRkmjetRlaVRgjO9U3ShKwzhlUylvxKfHfd5A=
|
github.com/pion/ice/v2 v2.3.8/go.mod h1:DoMA9FvsfNTBVnjyRf2t4EhUkSp9tNrH77fMtPFYygQ=
|
||||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w=
|
||||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI=
|
||||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
|
||||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||||
github.com/pion/sctp v1.8.5 h1:JCc25nghnXWOlSn3OVtEnA9PjQ2JsxQbG+CXZ1UkJKQ=
|
|
||||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
|
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||||
|
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
||||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA=
|
||||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw=
|
||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU=
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA=
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
|
||||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
|
||||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||||
github.com/pion/webrtc/v3 v3.1.50 h1:wLMo1+re4WMZ9Kun9qcGcY+XoHkE3i0CXrrc0sjhVCk=
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
github.com/pion/webrtc/v3 v3.1.50/go.mod h1:y9n09weIXB+sjb9mi0GBBewNxo4TKUQm5qdtT5v3/X4=
|
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||||
|
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.11 h1:lfGKYZcG7ghCTQWn+zsD+icIIWL3qIfclEjBGk537+s=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.11/go.mod h1:fejQio1v8tKG4ntq4u8H4uDHsCNX6eX7bT093t4H+0E=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
@@ -94,48 +94,49 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
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.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
|
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
|
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -151,30 +152,40 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
9
go.mod
9
go.mod
@@ -3,15 +3,16 @@ module github.com/pion/mediadevices
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
|
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc
|
||||||
github.com/gen2brain/malgo v0.11.10
|
github.com/gen2brain/malgo v0.11.10
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
|
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
|
||||||
github.com/pion/interceptor v0.1.12
|
github.com/pion/interceptor v0.1.17
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/rtcp v1.2.10
|
github.com/pion/rtcp v1.2.10
|
||||||
github.com/pion/rtp v1.7.13
|
github.com/pion/rtp v1.7.13
|
||||||
github.com/pion/webrtc/v3 v3.1.50
|
github.com/pion/webrtc/v3 v3.2.11
|
||||||
golang.org/x/image v0.2.0
|
github.com/stretchr/testify v1.8.4
|
||||||
|
golang.org/x/image v0.8.0
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
|
109
go.sum
109
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
|
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc h1:7cMZ/f4xwkD3FUOcThPAm0uecSP5kSTUU/3RWsrmcww=
|
||||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
github.com/blackjack/webcam v0.0.0-20230509180125-87693b3f29dc/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -49,43 +49,43 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/ice/v2 v2.2.12 h1:n3M3lUMKQM5IoofhJo73D3qVla+mJN2nVvbSPq32Nig=
|
github.com/pion/ice/v2 v2.3.8 h1:/4vM7uFPJez3PhNhlqUcJhboYaDNWo+R8oAuMj2cKsA=
|
||||||
github.com/pion/ice/v2 v2.2.12/go.mod h1:z2KXVFyRkmjetRlaVRgjO9U3ShKwzhlUylvxKfHfd5A=
|
github.com/pion/ice/v2 v2.3.8/go.mod h1:DoMA9FvsfNTBVnjyRf2t4EhUkSp9tNrH77fMtPFYygQ=
|
||||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w=
|
||||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI=
|
||||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
|
||||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||||
github.com/pion/sctp v1.8.5 h1:JCc25nghnXWOlSn3OVtEnA9PjQ2JsxQbG+CXZ1UkJKQ=
|
|
||||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
|
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||||
|
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
||||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA=
|
||||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw=
|
||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU=
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA=
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
|
||||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
|
||||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||||
github.com/pion/webrtc/v3 v3.1.50 h1:wLMo1+re4WMZ9Kun9qcGcY+XoHkE3i0CXrrc0sjhVCk=
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
github.com/pion/webrtc/v3 v3.1.50/go.mod h1:y9n09weIXB+sjb9mi0GBBewNxo4TKUQm5qdtT5v3/X4=
|
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||||
|
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.11 h1:lfGKYZcG7ghCTQWn+zsD+icIIWL3qIfclEjBGk537+s=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.11/go.mod h1:fejQio1v8tKG4ntq4u8H4uDHsCNX6eX7bT093t4H+0E=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
@@ -93,46 +93,47 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
|
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
|
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -146,29 +147,39 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#define MAX_DEVICES 8
|
#define MAX_DEVICES 8
|
||||||
#define MAX_PROPERTIES 64
|
#define MAX_PROPERTIES 64
|
||||||
#define MAX_DEVICE_UID_CHARS 64
|
#define MAX_DEVICE_UID_CHARS 64
|
||||||
|
#define MAX_DEVICE_NAME_CHARS 64
|
||||||
|
|
||||||
typedef const char* STATUS;
|
typedef const char* STATUS;
|
||||||
static STATUS STATUS_OK = (STATUS) NULL;
|
static STATUS STATUS_OK = (STATUS) NULL;
|
||||||
@@ -46,7 +47,7 @@ typedef enum AVBindFrameFormat {
|
|||||||
AVBindFrameFormatI420,
|
AVBindFrameFormatI420,
|
||||||
AVBindFrameFormatNV21,
|
AVBindFrameFormatNV21,
|
||||||
AVBindFrameFormatNV12,
|
AVBindFrameFormatNV12,
|
||||||
AVBindFrameFormatYUY2,
|
AVBindFrameFormatYUYV,
|
||||||
AVBindFrameFormatUYVY,
|
AVBindFrameFormatUYVY,
|
||||||
} AVBindFrameFormat;
|
} AVBindFrameFormat;
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ typedef struct AVBindSession AVBindSession, *PAVBindSession;
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char uid[MAX_DEVICE_UID_CHARS + 1];
|
char uid[MAX_DEVICE_UID_CHARS + 1];
|
||||||
|
char name[MAX_DEVICE_NAME_CHARS + 1];
|
||||||
} AVBindDevice, *PAVBindDevice;
|
} AVBindDevice, *PAVBindDevice;
|
||||||
|
|
||||||
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
||||||
|
@@ -139,7 +139,7 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
|||||||
case AVBindFrameFormatUYVY:
|
case AVBindFrameFormatUYVY:
|
||||||
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
||||||
break;
|
break;
|
||||||
case AVBindFrameFormatYUY2:
|
case AVBindFrameFormatYUYV:
|
||||||
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
|
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
|
||||||
break;
|
break;
|
||||||
// TODO: Add the rest of frame formats
|
// TODO: Add the rest of frame formats
|
||||||
@@ -163,7 +163,7 @@ STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
|||||||
*pFormat = AVBindFrameFormatUYVY;
|
*pFormat = AVBindFrameFormatUYVY;
|
||||||
break;
|
break;
|
||||||
case kCVPixelFormatType_422YpCbCr8_yuvs:
|
case kCVPixelFormatType_422YpCbCr8_yuvs:
|
||||||
*pFormat = AVBindFrameFormatYUY2;
|
*pFormat = AVBindFrameFormatYUYV;
|
||||||
break;
|
break;
|
||||||
// TODO: Add the rest of frame formats
|
// TODO: Add the rest of frame formats
|
||||||
default:
|
default:
|
||||||
@@ -201,6 +201,8 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
|
|||||||
pDevice = devices + i;
|
pDevice = devices + i;
|
||||||
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
||||||
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
||||||
|
strncpy(pDevice->name, refDevice.localizedName.UTF8String, MAX_DEVICE_NAME_CHARS);
|
||||||
|
pDevice->name[MAX_DEVICE_NAME_CHARS] = '\0';
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -34,6 +34,7 @@ type Device struct {
|
|||||||
// UID is a unique identifier for a device
|
// UID is a unique identifier for a device
|
||||||
UID string
|
UID string
|
||||||
cDevice C.AVBindDevice
|
cDevice C.AVBindDevice
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
||||||
@@ -44,8 +45,8 @@ func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
|||||||
return C.AVBindFrameFormatNV21, true
|
return C.AVBindFrameFormatNV21, true
|
||||||
case frame.FormatNV12:
|
case frame.FormatNV12:
|
||||||
return C.AVBindFrameFormatNV12, true
|
return C.AVBindFrameFormatNV12, true
|
||||||
case frame.FormatYUY2:
|
case frame.FormatYUYV:
|
||||||
return C.AVBindFrameFormatYUY2, true
|
return C.AVBindFrameFormatYUYV, true
|
||||||
case frame.FormatUYVY:
|
case frame.FormatUYVY:
|
||||||
return C.AVBindFrameFormatUYVY, true
|
return C.AVBindFrameFormatUYVY, true
|
||||||
default:
|
default:
|
||||||
@@ -61,8 +62,8 @@ func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
|
|||||||
return frame.FormatNV21, true
|
return frame.FormatNV21, true
|
||||||
case C.AVBindFrameFormatNV12:
|
case C.AVBindFrameFormatNV12:
|
||||||
return frame.FormatNV12, true
|
return frame.FormatNV12, true
|
||||||
case C.AVBindFrameFormatYUY2:
|
case C.AVBindFrameFormatYUYV:
|
||||||
return frame.FormatYUY2, true
|
return frame.FormatYUYV, true
|
||||||
case C.AVBindFrameFormatUYVY:
|
case C.AVBindFrameFormatUYVY:
|
||||||
return frame.FormatUYVY, true
|
return frame.FormatUYVY, true
|
||||||
default:
|
default:
|
||||||
@@ -87,6 +88,7 @@ func Devices(mediaType MediaType) ([]Device, error) {
|
|||||||
for i := range devices {
|
for i := range devices {
|
||||||
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
||||||
devices[i].cDevice = cDevices[i]
|
devices[i].cDevice = cDevices[i]
|
||||||
|
devices[i].Name = C.GoString(&cDevices[i].name[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices, nil
|
return devices, nil
|
||||||
|
@@ -64,10 +64,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
imgReal := img.(*image.YCbCr)
|
imgReal := img.(*image.YCbCr)
|
||||||
var y, cb, cr C.Slice
|
var y, cb, cr C.Slice
|
||||||
y.data = (*C.uchar)(&imgReal.Y[0])
|
y.data = (*C.uchar)(&imgReal.Y[0])
|
||||||
|
@@ -65,10 +65,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
|
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
bounds := yuvImg.Bounds()
|
bounds := yuvImg.Bounds()
|
||||||
|
@@ -304,10 +304,11 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||||
|
@@ -293,10 +293,11 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||||
|
@@ -219,10 +219,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
bounds := yuvImg.Bounds()
|
bounds := yuvImg.Bounds()
|
||||||
height := C.int(bounds.Dy())
|
height := C.int(bounds.Dy())
|
||||||
|
@@ -102,10 +102,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
var rc C.int
|
var rc C.int
|
||||||
|
28
pkg/driver/availability/error.go
Normal file
28
pkg/driver/availability/error.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package availability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnimplemented = NewError("not implemented")
|
||||||
|
ErrBusy = NewError("device or resource busy")
|
||||||
|
ErrNoDevice = NewError("no such device")
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorString struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(text string) error {
|
||||||
|
return &errorString{text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsError(err error) bool {
|
||||||
|
var target *errorString
|
||||||
|
return errors.As(err, &target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorString) Error() string {
|
||||||
|
return e.s
|
||||||
|
}
|
@@ -32,6 +32,7 @@ func Initialize() {
|
|||||||
driver.GetManager().Register(cam, driver.Info{
|
driver.GetManager().Register(cam, driver.Info{
|
||||||
Label: device.UID,
|
Label: device.UID,
|
||||||
DeviceType: driver.Camera,
|
DeviceType: driver.Camera,
|
||||||
|
Name: device.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,10 +8,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||||
|
|
||||||
"github.com/blackjack/webcam"
|
"github.com/blackjack/webcam"
|
||||||
"github.com/pion/mediadevices/pkg/driver"
|
"github.com/pion/mediadevices/pkg/driver"
|
||||||
@@ -60,6 +65,8 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const bufCount = 2
|
||||||
|
|
||||||
// Camera implementation using v4l2
|
// Camera implementation using v4l2
|
||||||
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
||||||
type camera struct {
|
type camera struct {
|
||||||
@@ -70,6 +77,7 @@ type camera struct {
|
|||||||
started bool
|
started bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
cancel func()
|
cancel func()
|
||||||
|
prevFrameTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -79,6 +87,7 @@ func init() {
|
|||||||
// Initialize finds and registers camera devices. This is part of an experimental API.
|
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||||
func Initialize() {
|
func Initialize() {
|
||||||
discovered := make(map[string]struct{})
|
discovered := make(map[string]struct{})
|
||||||
|
discover(discovered, "/dev/v4l/by-id/*")
|
||||||
discover(discovered, "/dev/v4l/by-path/*")
|
discover(discovered, "/dev/v4l/by-path/*")
|
||||||
discover(discovered, "/dev/video*")
|
discover(discovered, "/dev/video*")
|
||||||
}
|
}
|
||||||
@@ -107,7 +116,21 @@ func discover(discovered map[string]struct{}, pattern string) {
|
|||||||
if reallink == prioritizedDevice {
|
if reallink == prioritizedDevice {
|
||||||
priority = driver.PriorityHigh
|
priority = driver.PriorityHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var name, busInfo string
|
||||||
|
if webcamCam, err := webcam.Open(cam.path); err == nil {
|
||||||
|
name, _ = webcamCam.GetName()
|
||||||
|
busInfo, _ = webcamCam.GetBusInfo()
|
||||||
|
}
|
||||||
|
|
||||||
driver.GetManager().Register(cam, driver.Info{
|
driver.GetManager().Register(cam, driver.Info{
|
||||||
|
// Source: https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-querycap.html
|
||||||
|
// Name of the device, a NUL-terminated UTF-8 string. For example: “Yoyodyne TV/FM”. One driver may support
|
||||||
|
// different brands or models of video hardware. This information is intended for users, for example in a
|
||||||
|
// menu of available devices. Since multiple TV cards of the same brand may be installed which are
|
||||||
|
// supported by the same driver, this name should be combined with the character device file name
|
||||||
|
// (e.g. /dev/video2) or the bus_info string to avoid ambiguities.
|
||||||
|
Name: name + LabelSeparator + busInfo,
|
||||||
Label: label + LabelSeparator + reallink,
|
Label: label + LabelSeparator + reallink,
|
||||||
DeviceType: driver.Camera,
|
DeviceType: driver.Camera,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
@@ -158,8 +181,13 @@ func (c *camera) Open() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Late frames should be discarded. Buffering should be handled in higher level.
|
// Buffering should be handled in higher level.
|
||||||
cam.SetBufferCount(1)
|
err = cam.SetBufferCount(bufCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.prevFrameTime = time.Now()
|
||||||
c.cam = cam
|
c.cam = cam
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -228,6 +256,13 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.DiscardFramesOlderThan != 0 && time.Now().Sub(c.prevFrameTime) >= p.DiscardFramesOlderThan {
|
||||||
|
for i := 0; i < bufCount; i++ {
|
||||||
|
_ = cam.WaitForFrame(readTimeoutSec)
|
||||||
|
_, _ = cam.ReadFrame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := cam.WaitForFrame(readTimeoutSec)
|
err := cam.WaitForFrame(readTimeoutSec)
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
@@ -244,6 +279,10 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
|||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.DiscardFramesOlderThan != 0 {
|
||||||
|
c.prevFrameTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
// Frame is empty.
|
// Frame is empty.
|
||||||
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
|
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
@@ -277,13 +316,18 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
|
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
|
||||||
properties = append(properties, prop.Media{
|
for _, framerate := range c.cam.GetSupportedFramerates(format, frameSize.MinWidth, frameSize.MinHeight) {
|
||||||
Video: prop.Video{
|
for _, fps := range enumFramerate(framerate) {
|
||||||
Width: int(frameSize.MaxWidth),
|
properties = append(properties, prop.Media{
|
||||||
Height: int(frameSize.MaxHeight),
|
Video: prop.Video{
|
||||||
FrameFormat: supportedFormat,
|
Width: int(frameSize.MaxWidth),
|
||||||
},
|
Height: int(frameSize.MaxHeight),
|
||||||
})
|
FrameFormat: supportedFormat,
|
||||||
|
FrameRate: fps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
|
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
|
||||||
for _, supportedResolution := range supportedResolutions {
|
for _, supportedResolution := range supportedResolutions {
|
||||||
@@ -302,16 +346,88 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
properties = append(properties, prop.Media{
|
for _, framerate := range c.cam.GetSupportedFramerates(format, uint32(width), uint32(height)) {
|
||||||
Video: prop.Video{
|
for _, fps := range enumFramerate(framerate) {
|
||||||
Width: width,
|
properties = append(properties, prop.Media{
|
||||||
Height: height,
|
Video: prop.Video{
|
||||||
FrameFormat: supportedFormat,
|
Width: width,
|
||||||
},
|
Height: height,
|
||||||
})
|
FrameFormat: supportedFormat,
|
||||||
|
FrameRate: fps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return properties
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *camera) IsAvailable() (bool, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// close the opened file descriptor as quickly as possible and in all cases, including panics
|
||||||
|
func() {
|
||||||
|
var cam *webcam.Webcam
|
||||||
|
if cam, err = webcam.Open(c.path); err == nil {
|
||||||
|
defer cam.Close()
|
||||||
|
var index int32
|
||||||
|
// "Drivers must implement all the input ioctls when the device has one or more inputs..."
|
||||||
|
// Source: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/video.html?highlight=vidioc_enuminput
|
||||||
|
if index, err = cam.GetInput(); err == nil {
|
||||||
|
err = cam.SelectInput(uint32(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errno syscall.Errno
|
||||||
|
errors.As(err, &errno)
|
||||||
|
|
||||||
|
// See https://man7.org/linux/man-pages/man3/errno.3.html
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return true, nil
|
||||||
|
case errno == syscall.EBUSY:
|
||||||
|
return false, availability.ErrBusy
|
||||||
|
case errno == syscall.ENODEV || errno == syscall.ENOENT:
|
||||||
|
return false, availability.ErrNoDevice
|
||||||
|
default:
|
||||||
|
return false, availability.NewError(errno.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumFramerate returns a list of fps options from a FrameRate struct.
|
||||||
|
// discrete framerates will return a list of 1 fps element.
|
||||||
|
// stepwise framerates will return a list of all possible fps options.
|
||||||
|
func enumFramerate(framerate webcam.FrameRate) []float32 {
|
||||||
|
var framerates []float32
|
||||||
|
if framerate.StepNumerator == 0 && framerate.StepDenominator == 0 {
|
||||||
|
fr, err := calcFramerate(framerate.MaxNumerator, framerate.MaxDenominator)
|
||||||
|
if err != nil {
|
||||||
|
return framerates
|
||||||
|
}
|
||||||
|
framerates = append(framerates, fr)
|
||||||
|
} else {
|
||||||
|
for n := framerate.MinNumerator; n <= framerate.MaxNumerator; n += framerate.StepNumerator {
|
||||||
|
for d := framerate.MinDenominator; d <= framerate.MaxDenominator; d += framerate.StepDenominator {
|
||||||
|
fr, err := calcFramerate(n, d)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
framerates = append(framerates, fr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return framerates
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcFramerate turns fraction into a float32 fps value.
|
||||||
|
func calcFramerate(numerator uint32, denominator uint32) (float32, error) {
|
||||||
|
if denominator == 0 {
|
||||||
|
return 0, errors.New("framerate denominator is zero")
|
||||||
|
}
|
||||||
|
// round to three decimal places to avoid floating point precision issues
|
||||||
|
return float32(math.Round(1000.0/((float64(numerator))/float64(denominator))) / 1000), nil
|
||||||
|
}
|
||||||
|
@@ -57,6 +57,68 @@ func TestDiscover(t *testing.T) {
|
|||||||
drvs[0].Info().Label,
|
drvs[0].Info().Label,
|
||||||
drvs[1].Info().Label,
|
drvs[1].Info().Label,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returned drivers are unordered. Sort to get static result.
|
||||||
|
sort.Sort(sort.StringSlice(labels))
|
||||||
|
|
||||||
|
expected := longName + LabelSeparator + shortName
|
||||||
|
if label := labels[0]; label != expected {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expected, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNoLink := shortName2 + LabelSeparator + shortName2
|
||||||
|
if label := labels[1]; label != expectedNoLink {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoverByID(t *testing.T) {
|
||||||
|
const (
|
||||||
|
shortName = "id-unittest-video0"
|
||||||
|
shortName2 = "id-unittest-video1"
|
||||||
|
longName = "id-unittest-long-device-name:0:1:2:3"
|
||||||
|
)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
byIdDir := filepath.Join(dir, "v4l", "by-id")
|
||||||
|
if err := os.MkdirAll(byIdDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(
|
||||||
|
filepath.Join(dir, shortName),
|
||||||
|
filepath.Join(byIdDir, longName),
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
discovered := make(map[string]struct{})
|
||||||
|
discover(discovered, filepath.Join(byIdDir, "*"))
|
||||||
|
discover(discovered, filepath.Join(dir, "id-unittest-video*"))
|
||||||
|
|
||||||
|
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
|
||||||
|
// Ignore real cameras.
|
||||||
|
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "id-unittest")
|
||||||
|
})
|
||||||
|
if len(drvs) != 2 {
|
||||||
|
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{
|
||||||
|
drvs[0].Info().Label,
|
||||||
|
drvs[1].Info().Label,
|
||||||
|
}
|
||||||
|
|
||||||
// Returned drivers are unordered. Sort to get static result.
|
// Returned drivers are unordered. Sort to get static result.
|
||||||
sort.Sort(sort.StringSlice(labels))
|
sort.Sort(sort.StringSlice(labels))
|
||||||
|
|
||||||
@@ -98,3 +160,34 @@ func TestGetCameraReadTimeout(t *testing.T) {
|
|||||||
t.Errorf("Expected: %d, got: %d", expected, value)
|
t.Errorf("Expected: %d, got: %d", expected, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCalcFramerate(t *testing.T) {
|
||||||
|
framerates := []struct {
|
||||||
|
numerator uint32
|
||||||
|
denominator uint32
|
||||||
|
expected float32
|
||||||
|
}{
|
||||||
|
{1, 10, 10.0},
|
||||||
|
{1, 15, 15.0},
|
||||||
|
{1, 30, 30.0},
|
||||||
|
{1, 60, 60.0},
|
||||||
|
{1, 120, 120.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, framerate := range framerates {
|
||||||
|
value, err := calcFramerate(framerate.numerator, framerate.denominator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// make sure we do not have any rounding errors
|
||||||
|
if value != framerate.expected {
|
||||||
|
t.Errorf("Expected: %f, got: %f", framerate.expected, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// divide by zero check
|
||||||
|
_, err := calcFramerate(1, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected divide by zero error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
@@ -30,6 +31,7 @@ type Info struct {
|
|||||||
Label string
|
Label string
|
||||||
DeviceType DeviceType
|
DeviceType DeviceType
|
||||||
Priority Priority
|
Priority Priority
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
@@ -44,3 +46,14 @@ type Driver interface {
|
|||||||
Info() Info
|
Info() Info
|
||||||
Status() State
|
Status() State
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AvailabilityAdapter interface {
|
||||||
|
IsAvailable() (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAvailable(d Driver) (bool, error) {
|
||||||
|
if aa, ok := d.(AvailabilityAdapter); ok {
|
||||||
|
return aa.IsAvailable()
|
||||||
|
}
|
||||||
|
return false, availability.ErrUnimplemented
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// FilterFn is being used to decide if a driver should be included in the
|
// FilterFn is being used to decide if a driver should be included in the
|
||||||
// query result.
|
// query result.
|
||||||
type FilterFn func(Driver) bool
|
type FilterFn func(Driver) bool
|
||||||
@@ -55,6 +57,7 @@ func FilterNot(filter FilterFn) FilterFn {
|
|||||||
|
|
||||||
// Manager is a singleton to manage multiple drivers and their states
|
// Manager is a singleton to manage multiple drivers and their states
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
mu sync.Mutex
|
||||||
drivers map[string]Driver
|
drivers map[string]Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,8 @@ func GetManager() *Manager {
|
|||||||
|
|
||||||
// Register registers adapter to be discoverable by Query
|
// Register registers adapter to be discoverable by Query
|
||||||
func (m *Manager) Register(a Adapter, info Info) error {
|
func (m *Manager) Register(a Adapter, info Info) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
d := wrapAdapter(a, info)
|
d := wrapAdapter(a, info)
|
||||||
m.drivers[d.ID()] = d
|
m.drivers[d.ID()] = d
|
||||||
return nil
|
return nil
|
||||||
@@ -76,6 +81,8 @@ func (m *Manager) Register(a Adapter, info Info) error {
|
|||||||
|
|
||||||
// Query queries by using f to filter drivers, and simply return the filtered results.
|
// Query queries by using f to filter drivers, and simply return the filtered results.
|
||||||
func (m *Manager) Query(f FilterFn) []Driver {
|
func (m *Manager) Query(f FilterFn) []Driver {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
results := make([]Driver, 0)
|
results := make([]Driver, 0)
|
||||||
for _, d := range m.drivers {
|
for _, d := range m.drivers {
|
||||||
if ok := f(d); ok {
|
if ok := f(d); ok {
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func filterTrue(d Driver) bool {
|
func filterTrue(_ Driver) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func filterFalse(d Driver) bool {
|
func filterFalse(_ Driver) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,3 +44,71 @@ func TestFilterAnd(t *testing.T) {
|
|||||||
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
|
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeVideoAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeVideoAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeVideoAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeVideoAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func (a *fakeVideoAdapter) VideoRecord(_ prop.Media) (r video.Reader, err error) { return nil, nil }
|
||||||
|
|
||||||
|
type fakeAudioAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeAudioAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeAudioAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeAudioAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func (a *fakeAudioAdapter) AudioRecord(_ prop.Media) (r audio.Reader, err error) { return nil, nil }
|
||||||
|
|
||||||
|
type fakeAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
|
||||||
|
va := &fakeVideoAdapter{}
|
||||||
|
err := m.Register(va, Info{})
|
||||||
|
assert.NoError(t, err, "cannot register video adapter")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 1)
|
||||||
|
|
||||||
|
aa := &fakeAudioAdapter{}
|
||||||
|
err = m.Register(aa, Info{})
|
||||||
|
assert.NoError(t, err, "cannot register audio adapter")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||||
|
|
||||||
|
a := &fakeAdapter{}
|
||||||
|
assert.Panics(t, func() { m.Register(a, Info{}) }, "should not register adapter that is neither audio nor video")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterSync(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
start := make(chan struct{})
|
||||||
|
race := func() {
|
||||||
|
<-start
|
||||||
|
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
go race()
|
||||||
|
go race()
|
||||||
|
close(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySync(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
start := make(chan struct{})
|
||||||
|
race := func() {
|
||||||
|
<-start
|
||||||
|
m.Query(filterTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
go race()
|
||||||
|
go race()
|
||||||
|
close(start)
|
||||||
|
// write while reading
|
||||||
|
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||||
|
}
|
||||||
|
@@ -68,6 +68,7 @@ func Initialize() {
|
|||||||
Label: device.ID.String(),
|
Label: device.ID.String(),
|
||||||
DeviceType: driver.Microphone,
|
DeviceType: driver.Microphone,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
|
Name: info.Name(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,13 @@
|
|||||||
go-vnc is a VNC library for Go, initially supporting VNC clients but
|
go-vnc is a VNC library for Go, initially supporting VNC clients but
|
||||||
with the goal of eventually implementing a VNC server.
|
with the goal of eventually implementing a VNC server.
|
||||||
|
|
||||||
This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143).
|
This library implements [RFC 6143][rfc6143].
|
||||||
|
|
||||||
|
## RFCs
|
||||||
|
### Implemented
|
||||||
|
- **RFC **: [The Remote Framebuffer Protocol][rfc6143]
|
||||||
|
|
||||||
|
[rfc6143]: http://tools.ietf.org/html/rfc6143
|
||||||
|
|
||||||
## Usage & Installation
|
## Usage & Installation
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package driver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
@@ -21,6 +22,10 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
|||||||
state: StateClosed,
|
state: StateClosed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if aa, ok := a.(AvailabilityAdapter); ok {
|
||||||
|
d.isAvailable = aa.IsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
switch v := a.(type) {
|
switch v := a.(type) {
|
||||||
case VideoRecorder:
|
case VideoRecorder:
|
||||||
// Only expose Driver and VideoRecorder interfaces
|
// Only expose Driver and VideoRecorder interfaces
|
||||||
@@ -28,7 +33,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
|||||||
r := &struct {
|
r := &struct {
|
||||||
Driver
|
Driver
|
||||||
VideoRecorder
|
VideoRecorder
|
||||||
}{d, d}
|
AvailabilityAdapter
|
||||||
|
}{d, d, d}
|
||||||
return r
|
return r
|
||||||
case AudioRecorder:
|
case AudioRecorder:
|
||||||
// Only expose Driver and AudioRecorder interfaces
|
// Only expose Driver and AudioRecorder interfaces
|
||||||
@@ -36,7 +42,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
|||||||
return &struct {
|
return &struct {
|
||||||
Driver
|
Driver
|
||||||
AudioRecorder
|
AudioRecorder
|
||||||
}{d, d}
|
AvailabilityAdapter
|
||||||
|
}{d, d, d}
|
||||||
default:
|
default:
|
||||||
panic("adapter has to be either VideoRecorder/AudioRecorder")
|
panic("adapter has to be either VideoRecorder/AudioRecorder")
|
||||||
}
|
}
|
||||||
@@ -46,9 +53,10 @@ type adapterWrapper struct {
|
|||||||
Adapter
|
Adapter
|
||||||
VideoRecorder
|
VideoRecorder
|
||||||
AudioRecorder
|
AudioRecorder
|
||||||
id string
|
id string
|
||||||
info Info
|
info Info
|
||||||
state State
|
state State
|
||||||
|
isAvailable func() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *adapterWrapper) ID() string {
|
func (w *adapterWrapper) ID() string {
|
||||||
@@ -104,3 +112,10 @@ func (w *adapterWrapper) AudioRecord(p prop.Media) (r audio.Reader, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *adapterWrapper) IsAvailable() (bool, error) {
|
||||||
|
if w.isAvailable == nil {
|
||||||
|
return false, availability.ErrUnimplemented
|
||||||
|
}
|
||||||
|
return w.isAvailable()
|
||||||
|
}
|
||||||
|
@@ -39,6 +39,10 @@ func (a *audioAdapterBrokenMock) AudioRecord(p prop.Media) (r audio.Reader, err
|
|||||||
return nil, recordErr
|
return nil, recordErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type availabilityAdapterMock struct{ videoAdapterMock }
|
||||||
|
|
||||||
|
func (a *availabilityAdapterMock) IsAvailable() (bool, error) { return true, nil }
|
||||||
|
|
||||||
func TestVideoWrapperState(t *testing.T) {
|
func TestVideoWrapperState(t *testing.T) {
|
||||||
var a videoAdapterMock
|
var a videoAdapterMock
|
||||||
d := wrapAdapter(&a, Info{})
|
d := wrapAdapter(&a, Info{})
|
||||||
@@ -136,3 +140,38 @@ func TestAudioWrapperWithBrokenRecorderState(t *testing.T) {
|
|||||||
t.Errorf("expected the status to be %v, but got %v", StateClosed, d.Status())
|
t.Errorf("expected the status to be %v, but got %v", StateClosed, d.Status())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWrapperAvailabilityAdapter(t *testing.T) {
|
||||||
|
var aa availabilityAdapterMock
|
||||||
|
d := wrapAdapter(&aa, Info{})
|
||||||
|
|
||||||
|
ok, err := IsAvailable(d)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected nil, but got %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected true, but got %v", ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v videoAdapterMock
|
||||||
|
d = wrapAdapter(&v, Info{})
|
||||||
|
|
||||||
|
ok, err = IsAvailable(d)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected err, but got %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Errorf("expected false, but got %v", ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a audioAdapterMock
|
||||||
|
d = wrapAdapter(&a, Info{})
|
||||||
|
|
||||||
|
ok, err = IsAvailable(d)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected err, but got %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Errorf("expected false, but got %v", ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,11 +2,86 @@ package frame
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Thank you to https://github.com/filiptc/gorbit/blob/fa87ff39b68a6706306f34c318e0b9a5a3c97110/image/overlay.go#L37-L40
|
||||||
|
for addMotionDht, dhtMarker, dht, and sosMarker. These are protected under the following license:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Philip Thomas Casado
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var (
|
||||||
|
dhtMarker = []byte{255, 196}
|
||||||
|
dht = []byte{1, 162, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250}
|
||||||
|
sosMarker = []byte{255, 218}
|
||||||
|
huffmanTableInfoLength = len(dhtMarker) + len(dht) + len(sosMarker)
|
||||||
|
uninitializedHuffmanTableError jpeg.FormatError = jpeg.FormatError("uninitialized Huffman table")
|
||||||
|
)
|
||||||
|
|
||||||
func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) {
|
func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) {
|
||||||
img, err := jpeg.Decode(bytes.NewReader(frame))
|
img, err := jpeg.Decode(bytes.NewReader(frame))
|
||||||
|
if err == nil {
|
||||||
|
return img, func() {}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.As(err, &uninitializedHuffmanTableError) {
|
||||||
|
if err.Error() == uninitializedHuffmanTableError.Error() {
|
||||||
|
img, err = jpeg.Decode(bytes.NewReader(addMotionDht(frame)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return img, func() {}, err
|
return img, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addMotionDht(frame []byte) []byte {
|
||||||
|
jpegParts := bytes.Split(frame, sosMarker)
|
||||||
|
if len(jpegParts) != 2 {
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
correctedFrame := make([]byte, len(jpegParts[0])+huffmanTableInfoLength+len(jpegParts[1]))
|
||||||
|
correctedFrameOffset := 0
|
||||||
|
|
||||||
|
copy(correctedFrame[correctedFrameOffset:], jpegParts[0])
|
||||||
|
correctedFrameOffset += len(jpegParts[0])
|
||||||
|
|
||||||
|
copy(correctedFrame[correctedFrameOffset:], dhtMarker)
|
||||||
|
correctedFrameOffset += len(dhtMarker)
|
||||||
|
|
||||||
|
copy(correctedFrame[correctedFrameOffset:], dht)
|
||||||
|
correctedFrameOffset += len(dht)
|
||||||
|
|
||||||
|
copy(correctedFrame[correctedFrameOffset:], sosMarker)
|
||||||
|
correctedFrameOffset += len(sosMarker)
|
||||||
|
|
||||||
|
copy(correctedFrame[correctedFrameOffset:], jpegParts[1])
|
||||||
|
return correctedFrame
|
||||||
|
}
|
||||||
|
51
pkg/frame/compressed_test.go
Normal file
51
pkg/frame/compressed_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package frame
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image/jpeg"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddMotionDht(t *testing.T) {
|
||||||
|
uninitializedHuffmanTableFrame, err := jpeg.Decode(bytes.NewReader(UninitializedHuffmanTable))
|
||||||
|
|
||||||
|
// Decode fails with an uninitialized Huffman table error for sample input
|
||||||
|
expectedErrorMessage := "invalid JPEG format: uninitialized Huffman table"
|
||||||
|
if err.Error() != expectedErrorMessage {
|
||||||
|
t.Fatalf("Wrong decode error result,\nexpected:\n%+v\ngot:\n%+v", expectedErrorMessage, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode passes after adding default Huffman table to
|
||||||
|
defaultHuffmanTableFrame, err := jpeg.Decode(bytes.NewReader(addMotionDht(UninitializedHuffmanTable)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected decode function to pass after adding default Huffman table. Failed with %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding default Huffman table to a valid frame without a Huffman table changes the table
|
||||||
|
if uninitializedHuffmanTableFrame == defaultHuffmanTableFrame {
|
||||||
|
t.Fatalf("Expected addMotionDht to update frame. Instead returned original frame")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that an improperly constructed frame does not get updated by addMotionDht
|
||||||
|
randomBytes := []byte{1, 2, 3, 4}
|
||||||
|
frame1, err := jpeg.Decode(bytes.NewReader(randomBytes))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected decode function to fail with random bytes but passed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
frame2, err := jpeg.Decode(bytes.NewReader(addMotionDht(randomBytes)))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected decode function to fail with random bytes but passed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame1 != frame2 {
|
||||||
|
t.Fatalf("addMotionDht updated the frame despite being improperly constructed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeMJPEG(t *testing.T) {
|
||||||
|
_, _, err := decodeMJPEG(UninitializedHuffmanTable, 640, 480)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected decode function to pass. Failed with %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
@@ -7,36 +7,38 @@ import (
|
|||||||
type Format string
|
type Format string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// FormatI420 https://www.fourcc.org/pixel-format/yuv-i420/
|
// FormatI420 https://wiki.videolan.org/YUV#I420
|
||||||
FormatI420 Format = "I420"
|
FormatI420 Format = "I420"
|
||||||
// FormatI444 is a YUV format without sub-sampling
|
// FormatI444 is a YUV format without sub-sampling
|
||||||
FormatI444 Format = "I444"
|
FormatI444 Format = "I444"
|
||||||
// FormatNV21 https://www.fourcc.org/pixel-format/yuv-nv21/
|
// FormatNV21 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
|
||||||
FormatNV21 = "NV21"
|
FormatNV21 = "NV21"
|
||||||
// FormatNV12 https://www.fourcc.org/pixel-format/yuv-nv12/
|
// FormatNV12 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
|
||||||
FormatNV12 = "NV12"
|
FormatNV12 = "NV12"
|
||||||
// FormatYUY2 https://www.fourcc.org/pixel-format/yuv-yuy2/
|
// FormatYUY2 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-yuyv.html
|
||||||
|
// YUY2 is what Windows calls YUYV
|
||||||
FormatYUY2 = "YUY2"
|
FormatYUY2 = "YUY2"
|
||||||
// FormatUYVY https://www.fourcc.org/pixel-format/yuv-uyvy/
|
// FormatYUYV https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-yuyv.html
|
||||||
|
FormatYUYV = "YUYV"
|
||||||
|
// FormatUYVY https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-uyvy.html
|
||||||
FormatUYVY = "UYVY"
|
FormatUYVY = "UYVY"
|
||||||
|
|
||||||
// FormatRGBA https://www.fourcc.org/pixel-format/rgb-rgba/
|
// FormatRGBA https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-rgb.html
|
||||||
FormatRGBA Format = "RGBA"
|
FormatRGBA Format = "RGBA"
|
||||||
|
|
||||||
// FormatMJPEG https://www.fourcc.org/mjpg/
|
// FormatMJPEG https://wiki.videolan.org/MJPEG
|
||||||
FormatMJPEG = "MJPEG"
|
FormatMJPEG = "MJPEG"
|
||||||
|
|
||||||
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
|
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
|
||||||
FormatZ16 = "Z16"
|
FormatZ16 = "Z16"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FormatYUYV = FormatYUY2
|
|
||||||
|
|
||||||
var decoderMap = map[Format]decoderFunc{
|
var decoderMap = map[Format]decoderFunc{
|
||||||
FormatI420: decodeI420,
|
FormatI420: decodeI420,
|
||||||
FormatNV21: decodeNV21,
|
FormatNV21: decodeNV21,
|
||||||
FormatNV12: decodeNV12,
|
FormatNV12: decodeNV12,
|
||||||
FormatYUY2: decodeYUY2,
|
FormatYUY2: decodeYUY2,
|
||||||
|
FormatYUYV: decodeYUY2,
|
||||||
FormatUYVY: decodeUYVY,
|
FormatUYVY: decodeUYVY,
|
||||||
FormatMJPEG: decodeMJPEG,
|
FormatMJPEG: decodeMJPEG,
|
||||||
FormatZ16: decodeZ16,
|
FormatZ16: decodeZ16,
|
||||||
|
3
pkg/frame/sampleFrames.go
Normal file
3
pkg/frame/sampleFrames.go
Normal file
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// imageToYCbCr converts src to *image.YCbCr and store it to dst
|
// imageToYCbCr converts src to *image.YCbCr and store it to dst
|
||||||
@@ -60,29 +61,61 @@ func imageToYCbCr(dst *image.YCbCr, src image.Image) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bytePool stores slices to be reused
|
||||||
|
// New method is not set as the slice size
|
||||||
|
// should be allocated according to subsample ratio
|
||||||
|
var bytesPool sync.Pool
|
||||||
|
|
||||||
// ToI420 converts r to a new reader that will output images in I420 format
|
// ToI420 converts r to a new reader that will output images in I420 format
|
||||||
func ToI420(r Reader) Reader {
|
func ToI420(r Reader) Reader {
|
||||||
var yuvImg image.YCbCr
|
var yuvImg image.YCbCr
|
||||||
|
|
||||||
|
getSlice := func(cLen int) []uint8 {
|
||||||
|
// Retrieve slice from pool
|
||||||
|
dst, ok := bytesPool.Get().([]byte)
|
||||||
|
|
||||||
|
// Compare value or capacity of retrieved object
|
||||||
|
// If less than expected, reallocate new object
|
||||||
|
if !ok || cap(dst) < 2*cLen {
|
||||||
|
// Allocating memory for Cb and Cr
|
||||||
|
dst = make([]byte, 2*cLen, 2*cLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
return ReaderFunc(func() (image.Image, func(), error) {
|
return ReaderFunc(func() (image.Image, func(), error) {
|
||||||
img, _, err := r.Read()
|
img, _, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var releaseFunc func() = func() {}
|
||||||
|
|
||||||
imageToYCbCr(&yuvImg, img)
|
imageToYCbCr(&yuvImg, img)
|
||||||
|
|
||||||
// Covert pixel format to I420
|
// Covert pixel format to I420
|
||||||
switch yuvImg.SubsampleRatio {
|
switch yuvImg.SubsampleRatio {
|
||||||
case image.YCbCrSubsampleRatio420:
|
case image.YCbCrSubsampleRatio420:
|
||||||
case image.YCbCrSubsampleRatio444:
|
case image.YCbCrSubsampleRatio444:
|
||||||
yuvImg = i444ToI420(yuvImg)
|
cLen := yuvImg.CStride * yuvImg.Rect.Dy() / 4
|
||||||
|
dst := getSlice(cLen)
|
||||||
|
yuvImg = i444ToI420(yuvImg, dst)
|
||||||
|
releaseFunc = func() {
|
||||||
|
bytesPool.Put(dst)
|
||||||
|
}
|
||||||
case image.YCbCrSubsampleRatio422:
|
case image.YCbCrSubsampleRatio422:
|
||||||
yuvImg = i422ToI420(yuvImg)
|
cLen := yuvImg.CStride * (yuvImg.Rect.Dy() / 2)
|
||||||
|
dst := getSlice(cLen)
|
||||||
|
yuvImg = i422ToI420(yuvImg, dst)
|
||||||
|
releaseFunc = func() {
|
||||||
|
bytesPool.Put(dst)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, func() {}, fmt.Errorf("unsupported pixel format: %s", yuvImg.SubsampleRatio)
|
return nil, releaseFunc, fmt.Errorf("unsupported pixel format: %s", yuvImg.SubsampleRatio)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &yuvImg, func() {}, nil
|
return &yuvImg, releaseFunc, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,10 +15,13 @@ import "C"
|
|||||||
// All functions switched at runtime must be declared also in convert_nocgo.go.
|
// All functions switched at runtime must be declared also in convert_nocgo.go.
|
||||||
const hasCGOConvert = true
|
const hasCGOConvert = true
|
||||||
|
|
||||||
func i444ToI420(img image.YCbCr) image.YCbCr {
|
func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||||
h := img.Rect.Dy()
|
h := img.Rect.Dy()
|
||||||
cLen := img.CStride * h / 4
|
cLen := img.CStride * h / 4
|
||||||
cbDst, crDst := make([]uint8, cLen), make([]uint8, cLen)
|
// Divide preallocated memory to cbDst and crDst
|
||||||
|
// and truncate cap and len to cLen
|
||||||
|
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
|
||||||
|
crDst = crDst[:cLen:cLen]
|
||||||
C.i444ToI420CGO(
|
C.i444ToI420CGO(
|
||||||
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
||||||
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
||||||
@@ -31,10 +34,13 @@ func i444ToI420(img image.YCbCr) image.YCbCr {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func i422ToI420(img image.YCbCr) image.YCbCr {
|
func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||||
h := img.Rect.Dy()
|
h := img.Rect.Dy()
|
||||||
cLen := img.CStride * (h / 2)
|
cLen := img.CStride * (h / 2)
|
||||||
cbDst, crDst := make([]uint8, cLen), make([]uint8, cLen)
|
// Divide preallocated memory to cbDst and crDst
|
||||||
|
// and truncate cap and len to cLen
|
||||||
|
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
|
||||||
|
crDst = crDst[:cLen:cLen]
|
||||||
C.i422ToI420CGO(
|
C.i422ToI420CGO(
|
||||||
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
(*C.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
||||||
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
||||||
|
@@ -10,13 +10,16 @@ import (
|
|||||||
|
|
||||||
const hasCGOConvert = false
|
const hasCGOConvert = false
|
||||||
|
|
||||||
func i444ToI420(img image.YCbCr) image.YCbCr {
|
func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||||
h := img.Rect.Dy()
|
h := img.Rect.Dy()
|
||||||
addrSrc0 := 0
|
addrSrc0 := 0
|
||||||
addrSrc1 := img.CStride
|
addrSrc1 := img.CStride
|
||||||
cLen := img.CStride * (h / 2)
|
cLen := img.CStride * (h / 4)
|
||||||
addrDst := 0
|
addrDst := 0
|
||||||
cbDst, crDst := make([]uint8, cLen), make([]uint8, cLen)
|
// Divide preallocated memory to cbDst and crDst
|
||||||
|
// and truncate cap and len to cLen
|
||||||
|
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
|
||||||
|
crDst = crDst[:cLen:cLen]
|
||||||
|
|
||||||
for i := 0; i < h/2; i++ {
|
for i := 0; i < h/2; i++ {
|
||||||
for j := 0; j < img.CStride/2; j++ {
|
for j := 0; j < img.CStride/2; j++ {
|
||||||
@@ -40,19 +43,22 @@ func i444ToI420(img image.YCbCr) image.YCbCr {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func i422ToI420(img image.YCbCr) image.YCbCr {
|
func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||||
h := img.Rect.Dy()
|
h := img.Rect.Dy()
|
||||||
addrSrc := 0
|
addrSrc := 0
|
||||||
cLen := img.CStride * (h / 2)
|
cLen := img.CStride * (h / 2)
|
||||||
cbDst, crDst := make([]uint8, cLen), make([]uint8, cLen)
|
// Divide preallocated memory to cbDst and crDst
|
||||||
|
// and truncate cap and len to cLen
|
||||||
|
cbDst, crDst := dst[:cLen:cLen], dst[cLen:]
|
||||||
|
crDst = crDst[:cLen:cLen]
|
||||||
addrDst := 0
|
addrDst := 0
|
||||||
|
|
||||||
for i := 0; i < h/2; i++ {
|
for i := 0; i < h/2; i++ {
|
||||||
for j := 0; j < img.CStride; j++ {
|
for j := 0; j < img.CStride; j++ {
|
||||||
cb := uint16(img.Cb[addrSrc]) + uint16(img.Cb[addrSrc+img.CStride])
|
cb := uint16(img.Cb[addrSrc]) + uint16(img.Cb[addrSrc+img.CStride])
|
||||||
cr := uint16(img.Cr[addrSrc]) + uint16(img.Cr[addrSrc+img.CStride])
|
cr := uint16(img.Cr[addrSrc]) + uint16(img.Cr[addrSrc+img.CStride])
|
||||||
cbDst[addrDst] = uint8(cb / 4)
|
cbDst[addrDst] = uint8(cb / 2)
|
||||||
crDst[addrDst] = uint8(cr / 4)
|
crDst[addrDst] = uint8(cr / 2)
|
||||||
addrSrc++
|
addrSrc++
|
||||||
addrDst++
|
addrDst++
|
||||||
}
|
}
|
||||||
|
@@ -147,10 +147,11 @@ func TestToI420(t *testing.T) {
|
|||||||
r := ToI420(ReaderFunc(func() (image.Image, func(), error) {
|
r := ToI420(ReaderFunc(func() (image.Image, func(), error) {
|
||||||
return c.src, func() {}, nil
|
return c.src, func() {}, nil
|
||||||
}))
|
}))
|
||||||
out, _, err := r.Read()
|
out, release, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
if !reflect.DeepEqual(c.expected, out) {
|
if !reflect.DeepEqual(c.expected, out) {
|
||||||
t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out)
|
t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out)
|
||||||
}
|
}
|
||||||
@@ -230,10 +231,11 @@ func BenchmarkToI420(b *testing.B) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _, err := r.Read()
|
_, release, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Unexpected error: %v", err)
|
b.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
release()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -526,6 +526,9 @@ func TestScale(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestScaleFastBoxSampling(t *testing.T) {
|
func TestScaleFastBoxSampling(t *testing.T) {
|
||||||
|
if !hasCGOConvert {
|
||||||
|
t.Skip("Skip: nocgo implementation is not supported for FastBoxSampling")
|
||||||
|
}
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
src image.Image
|
src image.Image
|
||||||
width, height int
|
width, height int
|
||||||
@@ -535,19 +538,19 @@ func TestScaleFastBoxSampling(t *testing.T) {
|
|||||||
src: &image.YCbCr{
|
src: &image.YCbCr{
|
||||||
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
Y: []uint8{
|
Y: []uint8{
|
||||||
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
0xF0, 0xF8, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
||||||
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
0xF8, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
|
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
|
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
|
||||||
},
|
},
|
||||||
Cb: []uint8{
|
Cb: []uint8{
|
||||||
0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
|
0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
|
||||||
0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
|
0x20, 0x20, 0x80, 0x80, 0x50, 0x50,
|
||||||
0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30,
|
0x80, 0x80, 0xE0, 0xE0, 0x34, 0x34,
|
||||||
0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30,
|
0x80, 0x80, 0xE0, 0xE0, 0x30, 0x30,
|
||||||
},
|
},
|
||||||
Cr: []uint8{
|
Cr: []uint8{
|
||||||
@@ -565,24 +568,104 @@ func TestScaleFastBoxSampling(t *testing.T) {
|
|||||||
expected: &image.YCbCr{
|
expected: &image.YCbCr{
|
||||||
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
Y: []uint8{
|
Y: []uint8{
|
||||||
0xF0, 0x80, 0x08, 0x00, 0x78, 0x80,
|
0xF4, 0x10, 0x00, 0x00, 0xF0, 0x10,
|
||||||
0x08, 0x00, 0x20, 0x20, 0x20, 0x20,
|
0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x04, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
0x00, 0x40, 0x58, 0x18, 0x18, 0x18,
|
0x00, 0x80, 0x30, 0x00, 0x30, 0x00,
|
||||||
},
|
},
|
||||||
Cb: []uint8{
|
Cb: []uint8{
|
||||||
0x20, 0x50, 0x68,
|
0x20, 0x80, 0x50,
|
||||||
0x68, 0xB0, 0x88,
|
0x80, 0xE0, 0x32,
|
||||||
},
|
},
|
||||||
Cr: []uint8{
|
Cr: []uint8{
|
||||||
0xE0, 0xB0, 0x98,
|
0xE0, 0x80, 0xB0,
|
||||||
0xD0, 0x98, 0x80,
|
0xF0, 0x40, 0xC0,
|
||||||
},
|
},
|
||||||
YStride: 6,
|
YStride: 6,
|
||||||
CStride: 3,
|
CStride: 3,
|
||||||
Rect: image.Rect(0, 0, 6, 4),
|
Rect: image.Rect(0, 0, 6, 4),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"I420Uneven6x4to3x2": {
|
||||||
|
src: &image.YCbCr{
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Y: []uint8{
|
||||||
|
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
|
||||||
|
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
|
||||||
|
},
|
||||||
|
Cb: []uint8{
|
||||||
|
0x20, 0x20, 0x80,
|
||||||
|
0x20, 0x20, 0x80,
|
||||||
|
},
|
||||||
|
Cr: []uint8{
|
||||||
|
0xE0, 0xE0, 0x80,
|
||||||
|
0xE0, 0xE0, 0x80,
|
||||||
|
0xFF, // dummy data to detect out of range read
|
||||||
|
},
|
||||||
|
YStride: 6,
|
||||||
|
CStride: 3,
|
||||||
|
Rect: image.Rect(0, 0, 6, 3),
|
||||||
|
},
|
||||||
|
width: 4,
|
||||||
|
height: 2,
|
||||||
|
expected: &image.YCbCr{
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Y: []uint8{
|
||||||
|
0xF0, 0x10, 0x08, 0x00,
|
||||||
|
0x00, 0x00, 0x20, 0x40,
|
||||||
|
},
|
||||||
|
Cb: []uint8{
|
||||||
|
0x20, 0x80,
|
||||||
|
},
|
||||||
|
Cr: []uint8{
|
||||||
|
0xE0, 0x80,
|
||||||
|
},
|
||||||
|
YStride: 4,
|
||||||
|
CStride: 2,
|
||||||
|
Rect: image.Rect(0, 0, 4, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"I420Uneven6x3to5x2": {
|
||||||
|
src: &image.YCbCr{
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Y: []uint8{
|
||||||
|
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
|
||||||
|
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x50,
|
||||||
|
},
|
||||||
|
Cb: []uint8{
|
||||||
|
0x20, 0x20, 0x80,
|
||||||
|
0x20, 0x20, 0x80,
|
||||||
|
},
|
||||||
|
Cr: []uint8{
|
||||||
|
0xE0, 0xE0, 0x80,
|
||||||
|
0xE0, 0xE0, 0x80,
|
||||||
|
0xFF, // dummy data to detect out of range read
|
||||||
|
},
|
||||||
|
YStride: 6,
|
||||||
|
CStride: 3,
|
||||||
|
Rect: image.Rect(0, 0, 6, 3),
|
||||||
|
},
|
||||||
|
width: 5,
|
||||||
|
height: 2,
|
||||||
|
expected: &image.YCbCr{
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Y: []uint8{
|
||||||
|
0xF0, 0x10, 0x10, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x40, 0x50,
|
||||||
|
},
|
||||||
|
Cb: []uint8{
|
||||||
|
0x20, 0x80, 0x00,
|
||||||
|
},
|
||||||
|
Cr: []uint8{
|
||||||
|
0xE0, 0x80, 0x00,
|
||||||
|
},
|
||||||
|
YStride: 5,
|
||||||
|
CStride: 3,
|
||||||
|
Rect: image.Rect(0, 0, 5, 2),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, c := range cases {
|
for name, c := range cases {
|
||||||
|
@@ -29,7 +29,7 @@ void fastBoxSampling(
|
|||||||
const int sw, const int sh, const int sstride,
|
const int sw, const int sh, const int sstride,
|
||||||
uint32_t* tmp)
|
uint32_t* tmp)
|
||||||
{
|
{
|
||||||
memset(tmp, 0, dw * dh * ch * sizeof(tmp[0]));
|
memset(tmp, 0, dstride * dh * ch * sizeof(tmp[0]));
|
||||||
|
|
||||||
for (int sy = 0; sy < sh; sy++)
|
for (int sy = 0; sy < sh; sy++)
|
||||||
{
|
{
|
||||||
@@ -39,13 +39,14 @@ void fastBoxSampling(
|
|||||||
uint32_t* tmp2 = &tmp[ty * dstride];
|
uint32_t* tmp2 = &tmp[ty * dstride];
|
||||||
for (int sx = 0; sx < sw * ch; sx += ch)
|
for (int sx = 0; sx < sw * ch; sx += ch)
|
||||||
{
|
{
|
||||||
if (tx * sw < sx * dw)
|
|
||||||
tx += ch;
|
|
||||||
|
|
||||||
for (int c = 0; c < ch; c++)
|
for (int c = 0; c < ch; c++)
|
||||||
{
|
{
|
||||||
tmp2[tx + c] += 0x10000 | src2[sx + c];
|
tmp2[tx + c] += 0x10000 | src2[sx + c];
|
||||||
}
|
}
|
||||||
|
if (tx * sw < sx * dw)
|
||||||
|
{
|
||||||
|
tx += ch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
pkg/io/video/scaler_nocgo.go
Normal file
22
pkg/io/video/scaler_nocgo.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
// +build !cgo
|
||||||
|
|
||||||
|
package video
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScalerFastBoxSampling mock scaler for nocgo implementation to pass tests for CGO_ENABLED=0
|
||||||
|
var (
|
||||||
|
ScalerFastBoxSampling = Scaler(&FastBoxSampling{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// FastBoxSampling mock implementation for nocgo implementation
|
||||||
|
// TODO implement nocgo FastBoxSampling scaling algorithm
|
||||||
|
type FastBoxSampling struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FastBoxSampling) Scale(_ draw.Image, _ image.Rectangle, _ image.Image, _ image.Rectangle, _ draw.Op, _ *draw.Options) {
|
||||||
|
}
|
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MediaConstraints represents set of media propaty constraints.
|
// MediaConstraints represents set of media property constraints.
|
||||||
// Each field constrains property by min/ideal/max range, exact match, or oneof match.
|
// Each field constrains property by min/ideal/max range, exact match, or oneof match.
|
||||||
type MediaConstraints struct {
|
type MediaConstraints struct {
|
||||||
DeviceID StringConstraint
|
DeviceID StringConstraint
|
||||||
@@ -145,11 +145,10 @@ func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
|
|||||||
cmps.add(p.Width, o.Width)
|
cmps.add(p.Width, o.Width)
|
||||||
cmps.add(p.Height, o.Height)
|
cmps.add(p.Height, o.Height)
|
||||||
cmps.add(p.FrameFormat, o.FrameFormat)
|
cmps.add(p.FrameFormat, o.FrameFormat)
|
||||||
// The next line is comment out for now to not include framerate in the fitness function.
|
// skip framerate if not available in media properties
|
||||||
// As camera.Properties does not have access to the list of available framerate at the moment,
|
if o.FrameRate > 0.0 {
|
||||||
// no driver can be matched with a framerate constraint.
|
cmps.add(p.FrameRate, o.FrameRate)
|
||||||
// Note this also affect screen caputre as screen.Properties does not fill in the Framerate field.
|
}
|
||||||
// cmps.add(p.FrameRate, o.FrameRate)
|
|
||||||
cmps.add(p.SampleRate, o.SampleRate)
|
cmps.add(p.SampleRate, o.SampleRate)
|
||||||
cmps.add(p.Latency, o.Latency)
|
cmps.add(p.Latency, o.Latency)
|
||||||
cmps.add(p.ChannelCount, o.ChannelCount)
|
cmps.add(p.ChannelCount, o.ChannelCount)
|
||||||
@@ -229,16 +228,18 @@ func (c *comparisons) fitnessDistance() (float64, bool) {
|
|||||||
|
|
||||||
// VideoConstraints represents a video's constraints
|
// VideoConstraints represents a video's constraints
|
||||||
type VideoConstraints struct {
|
type VideoConstraints struct {
|
||||||
Width, Height IntConstraint
|
Width, Height IntConstraint
|
||||||
FrameRate FloatConstraint
|
FrameRate FloatConstraint
|
||||||
FrameFormat FrameFormatConstraint
|
FrameFormat FrameFormatConstraint
|
||||||
|
DiscardFramesOlderThan time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video represents a video's constraints
|
// Video represents a video's constraints
|
||||||
type Video struct {
|
type Video struct {
|
||||||
Width, Height int
|
Width, Height int
|
||||||
FrameRate float32
|
FrameRate float32
|
||||||
FrameFormat frame.Format
|
FrameFormat frame.Format
|
||||||
|
DiscardFramesOlderThan time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioConstraints represents an audio's constraints
|
// AudioConstraints represents an audio's constraints
|
||||||
|
@@ -85,6 +85,60 @@ func TestCompareMatch(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
"FloatExactMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: FloatExact(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 30.0,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FloatExactUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: FloatExact(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 30.1,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"FloatIdealMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: Float(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 30.0,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FloatIdealUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: Float(30),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 10.0,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FloatRangeMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: FloatRanged{Min: 30, Max: 40},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 35.0,
|
||||||
|
}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FloatRangeUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: FloatRanged{Min: 30, Max: 40},
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 50.0,
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
"FrameFormatOneOfUnmatch": {
|
"FrameFormatOneOfUnmatch": {
|
||||||
MediaConstraints{VideoConstraints: VideoConstraints{
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY},
|
FrameFormat: FrameFormatOneOf{frame.FormatYUYV, frame.FormatUYVY},
|
||||||
@@ -366,3 +420,63 @@ func TestString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFrameRateProps(t *testing.T) {
|
||||||
|
testDataSet := map[string]struct {
|
||||||
|
a MediaConstraints
|
||||||
|
b Media
|
||||||
|
score float64
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
"FrameRateIdealMatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: Float(30.0),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 30.0,
|
||||||
|
}},
|
||||||
|
0.0,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FrameRateIdealUnmatch": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: Float(30.0),
|
||||||
|
}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 60.0,
|
||||||
|
}},
|
||||||
|
0.5,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FrameRateConstraintMissing": {
|
||||||
|
// empty video fps constraint
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{}},
|
||||||
|
Media{Video: Video{
|
||||||
|
FrameRate: 30.0,
|
||||||
|
}},
|
||||||
|
0.0,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"FrameRatePropMissing": {
|
||||||
|
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||||
|
FrameRate: Float(30.0),
|
||||||
|
}},
|
||||||
|
// empty video fps property
|
||||||
|
Media{Video: Video{}},
|
||||||
|
0.0,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data := range testDataSet {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
score, match := data.a.FitnessDistance(data.b)
|
||||||
|
if score != data.score {
|
||||||
|
t.Errorf("expected score %f, got %f", data.score, score)
|
||||||
|
}
|
||||||
|
if match != data.match {
|
||||||
|
t.Errorf("expected match %t, got %t", data.match, match)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
17
track.go
17
track.go
@@ -83,6 +83,7 @@ type baseTrack struct {
|
|||||||
Source
|
Source
|
||||||
err error
|
err error
|
||||||
onErrorHandler func(error)
|
onErrorHandler func(error)
|
||||||
|
errMu sync.Mutex
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
endOnce sync.Once
|
endOnce sync.Once
|
||||||
kind MediaDeviceType
|
kind MediaDeviceType
|
||||||
@@ -129,10 +130,10 @@ func (track *baseTrack) RID() string {
|
|||||||
// OnEnded sets an error handler. When a track has been created and started, if an
|
// OnEnded sets an error handler. When a track has been created and started, if an
|
||||||
// error occurs, handler will get called with the error given to the parameter.
|
// error occurs, handler will get called with the error given to the parameter.
|
||||||
func (track *baseTrack) OnEnded(handler func(error)) {
|
func (track *baseTrack) OnEnded(handler func(error)) {
|
||||||
track.mu.Lock()
|
track.errMu.Lock()
|
||||||
track.onErrorHandler = handler
|
track.onErrorHandler = handler
|
||||||
err := track.err
|
err := track.err
|
||||||
track.mu.Unlock()
|
track.errMu.Unlock()
|
||||||
|
|
||||||
if err != nil && handler != nil {
|
if err != nil && handler != nil {
|
||||||
// Already errored.
|
// Already errored.
|
||||||
@@ -144,10 +145,10 @@ func (track *baseTrack) OnEnded(handler func(error)) {
|
|||||||
|
|
||||||
// onError is a callback when an error occurs
|
// onError is a callback when an error occurs
|
||||||
func (track *baseTrack) onError(err error) {
|
func (track *baseTrack) onError(err error) {
|
||||||
track.mu.Lock()
|
track.errMu.Lock()
|
||||||
track.err = err
|
track.err = err
|
||||||
handler := track.onErrorHandler
|
handler := track.onErrorHandler
|
||||||
track.mu.Unlock()
|
track.errMu.Unlock()
|
||||||
|
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
track.endOnce.Do(func() {
|
track.endOnce.Do(func() {
|
||||||
@@ -171,6 +172,14 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
|||||||
for _, wantedCodec := range ctx.CodecParameters() {
|
for _, wantedCodec := range ctx.CodecParameters() {
|
||||||
logger.Debugf("trying to build %s rtp reader", wantedCodec.MimeType)
|
logger.Debugf("trying to build %s rtp reader", wantedCodec.MimeType)
|
||||||
encodedReader, err = specializedTrack.NewRTPReader(wantedCodec.MimeType, uint32(ctx.SSRC()), rtpOutboundMTU)
|
encodedReader, err = specializedTrack.NewRTPReader(wantedCodec.MimeType, uint32(ctx.SSRC()), rtpOutboundMTU)
|
||||||
|
|
||||||
|
track.errMu.Lock()
|
||||||
|
if track.err != nil {
|
||||||
|
err = track.err
|
||||||
|
encodedReader = nil
|
||||||
|
}
|
||||||
|
track.errMu.Unlock()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
selectedCodec = wantedCodec
|
selectedCodec = wantedCodec
|
||||||
break
|
break
|
||||||
|
@@ -2,14 +2,33 @@ package mediadevices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/pion/interceptor"
|
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
|
"github.com/pion/webrtc/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errExpected error = errors.New("an error")
|
||||||
|
|
||||||
|
type DummyBindTrack struct {
|
||||||
|
*baseTrack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (track *DummyBindTrack) Bind(ctx webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) {
|
||||||
|
track.mu.Lock()
|
||||||
|
defer track.mu.Unlock()
|
||||||
|
|
||||||
|
track.onError(errExpected)
|
||||||
|
|
||||||
|
<-time.After(5 * time.Millisecond)
|
||||||
|
|
||||||
|
return webrtc.RTPCodecParameters{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestOnEnded(t *testing.T) {
|
func TestOnEnded(t *testing.T) {
|
||||||
errExpected := errors.New("an error")
|
|
||||||
|
|
||||||
t.Run("ErrorAfterRegister", func(t *testing.T) {
|
t.Run("ErrorAfterRegister", func(t *testing.T) {
|
||||||
tr := &baseTrack{}
|
tr := &baseTrack{}
|
||||||
@@ -54,6 +73,34 @@ func TestOnEnded(t *testing.T) {
|
|||||||
t.Error("Timeout")
|
t.Error("Timeout")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("ErrorDurringBind", func(t *testing.T) {
|
||||||
|
tr := &DummyBindTrack{
|
||||||
|
baseTrack: &baseTrack{
|
||||||
|
activePeerConnections: make(map[string]chan<- chan<- struct{}),
|
||||||
|
mu: sync.Mutex{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
called := make(chan error, 1)
|
||||||
|
tr.OnEnded(func(err error) {
|
||||||
|
called <- errExpected
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := tr.Bind(webrtc.TrackLocalContext{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-called:
|
||||||
|
if err != errExpected {
|
||||||
|
t.Errorf("Expected to receive error: %v, got: %v", errExpected, err)
|
||||||
|
}
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
t.Error("Timeout")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeRTCPReader struct {
|
type fakeRTCPReader struct {
|
||||||
|
Reference in New Issue
Block a user