mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 04:46:10 +08:00
Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
02d4e0e896 | ||
![]() |
38a3b829f6 | ||
![]() |
c4bb9eb649 | ||
![]() |
bc3bdc1855 | ||
![]() |
a6ffeb31ab | ||
![]() |
a9046e6ac1 | ||
![]() |
2bc011f39f | ||
![]() |
68df5b3eb5 | ||
![]() |
bf52b1af58 | ||
![]() |
ccee17d04c | ||
![]() |
f4f5a24ce4 | ||
![]() |
dec2300a95 | ||
![]() |
0c8f3cfc7a | ||
![]() |
6829d71e58 | ||
![]() |
1b36c0360d | ||
![]() |
5aa157e8fc | ||
![]() |
cd8e34f3ed | ||
![]() |
d03383a6fd | ||
![]() |
c4fd1de9a0 | ||
![]() |
47638e3290 | ||
![]() |
09b497727d | ||
![]() |
7ae82fbda7 | ||
![]() |
4477898296 | ||
![]() |
d5d41e9ca5 | ||
![]() |
9fb24fb036 | ||
![]() |
1cd1b136cc | ||
![]() |
08fb3e8a48 | ||
![]() |
4aae1bc842 | ||
![]() |
3c9fee958e | ||
![]() |
68092afe36 | ||
![]() |
cd92becd1c | ||
![]() |
2bad953124 | ||
![]() |
634addc04d | ||
![]() |
d0c1677cfb | ||
![]() |
b4770e5fbf | ||
![]() |
855441dad1 | ||
![]() |
2882fd42d5 | ||
![]() |
694b4abd83 | ||
![]() |
afb2f78e3c | ||
![]() |
b14ce7987c | ||
![]() |
728207526d | ||
![]() |
3582e5d017 | ||
![]() |
774b7de8d2 | ||
![]() |
96efc0932e | ||
![]() |
a1b4c0f69a | ||
![]() |
6bba5f1663 | ||
![]() |
79aef00e07 | ||
![]() |
03c44ee803 | ||
![]() |
dad145ef11 | ||
![]() |
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 | ||
![]() |
f8f8511d94 | ||
![]() |
85597da5bb | ||
![]() |
6ac1424488 | ||
![]() |
73a158d097 | ||
![]() |
cb23f1fa82 | ||
![]() |
a0f090dced | ||
![]() |
4f7542b614 | ||
![]() |
860f4a1490 | ||
![]() |
09f3bcc013 | ||
![]() |
f0ad407da8 | ||
![]() |
8d7ccada1c | ||
![]() |
2d9208de5b | ||
![]() |
52cf6e72b1 | ||
![]() |
7335797301 |
71
.github/workflows/ci.yaml
vendored
71
.github/workflows/ci.yaml
vendored
@@ -13,13 +13,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [ '1.18', '1.17' ]
|
||||
go:
|
||||
- '1.20'
|
||||
- '1.21'
|
||||
name: Linux Go ${{ matrix.go }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Install dependencies
|
||||
@@ -33,23 +35,66 @@ jobs:
|
||||
- name: Run Test Suite
|
||||
run: make test
|
||||
- uses: codecov/codecov-action@v3
|
||||
if: matrix.go == '1.18'
|
||||
if: matrix.go == '1.21'
|
||||
build-darwin:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [ '1.18', '1.17' ]
|
||||
name: Darwin Go ${{ matrix.go }}
|
||||
runs-on:
|
||||
- macos-latest
|
||||
- ['self-hosted', 'macOS', 'ARM64']
|
||||
go:
|
||||
- '1.20'
|
||||
- '1.21'
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
name: Darwin Go ${{ matrix.go }} ${{ join(matrix.runs-on, ' ') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
# Set up local brew only on self-hosted darwin
|
||||
- name: Checkout Homebrew
|
||||
if: matrix.runs-on != 'macos-latest'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Homebrew/brew
|
||||
path: homebrew
|
||||
- name: Local brew cache key
|
||||
if: matrix.runs-on != 'macos-latest'
|
||||
id: brew-cache-key
|
||||
run: echo "key=$(date +'%Y-%U')" | tee ${GITHUB_OUTPUT} # weekly update cache
|
||||
- name: Cache local brew taps
|
||||
if: matrix.runs-on != 'macos-latest'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: homebrew/Library/Taps
|
||||
key: ${{ runner.os }}-brew-taps-${{ steps.brew-cache-key.outputs.key }}
|
||||
restore-keys: ${{ runner.os }}-brew-taps-
|
||||
- name: Set up brew to install deps under temporary dir
|
||||
if: matrix.runs-on != 'macos-latest'
|
||||
run: |
|
||||
dir="${GITHUB_WORKSPACE}/homebrew"
|
||||
cd "${dir}"
|
||||
|
||||
echo "Set up shellenv" >&2
|
||||
env="$(./bin/brew shellenv)"
|
||||
echo "${env}" | tee -a ${GITHUB_ENV}
|
||||
eval "${env}"
|
||||
|
||||
echo "Set up paths" >&2
|
||||
echo "${dir}/bin" | tee -a ${GITHUB_PATH}
|
||||
|
||||
echo "Brew update" >&2
|
||||
brew update --force --quiet
|
||||
chmod -R go-w "$(brew --prefix)/share/zsh"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
which brew
|
||||
brew install \
|
||||
pkg-config \
|
||||
opus \
|
||||
@@ -62,12 +107,12 @@ jobs:
|
||||
name: Check Licenses
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.17'
|
||||
go-version: '1.21'
|
||||
- name: Installing go-licenses
|
||||
run: go get github.com/google/go-licenses
|
||||
run: go install github.com/google/go-licenses@latest
|
||||
- name: Checking licenses
|
||||
run: go-licenses check ./...
|
||||
|
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: fix
|
||||
|
10
Makefile
10
Makefile
@@ -25,7 +25,7 @@ codec_dir := pkg/codec
|
||||
codec_list := $(shell ls $(codec_dir)/*/Makefile)
|
||||
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
|
||||
targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms)))
|
||||
pkgs_without_mmal := $(shell go list ./... | grep -v mmal)
|
||||
pkgs_without_ext_device := $(shell go list ./... | grep -v mmal | grep -v vaapi)
|
||||
pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
|
||||
|
||||
define BUILD_TEMPLATE
|
||||
@@ -73,11 +73,11 @@ $(foreach codec, $(codec_list), \
|
||||
# Description:
|
||||
# Run a series of tests
|
||||
$(cmd_test):
|
||||
go vet $(pkgs_without_mmal)
|
||||
go build $(pkgs_without_mmal)
|
||||
go vet $(pkgs_without_ext_device)
|
||||
go build $(pkgs_without_ext_device)
|
||||
# go build without CGO
|
||||
CGO_ENABLED=0 go build $(pkgs_without_cgo)
|
||||
# go build with CGO
|
||||
CGO_ENABLED=1 go build $(pkgs_without_mmal)
|
||||
CGO_ENABLED=1 go build $(pkgs_without_ext_device)
|
||||
$(MAKE) --directory=$(examples_dir)
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_mmal)
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_ext_device)
|
||||
|
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>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<a href="https://github.com/pion/mediadevices/actions"><img src="https://github.com/pion/mediadevices/workflows/CI/badge.svg?branch=master" alt="Build status"></a>
|
||||
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://godoc.org/github.com/pion/mediadevices?status.svg" alt="GoDoc"></a>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/mediadevices/test.yaml">
|
||||
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://pkg.go.dev/badge/github.com/pion/mediadevices.svg" alt="Go Reference"></a>
|
||||
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
</p>
|
||||
@@ -15,11 +15,13 @@
|
||||
|
||||
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
|
||||
|
||||
## Install
|
||||
### Install
|
||||
|
||||
`go get -u github.com/pion/mediadevices`
|
||||
```bash
|
||||
go get -u github.com/pion/mediadevices
|
||||
```
|
||||
|
||||
## Usage
|
||||
### Usage
|
||||
|
||||
The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
|
||||
|
||||
@@ -67,25 +69,21 @@ func main() {
|
||||
output, _ := os.Create("frame.jpg")
|
||||
jpeg.Encode(output, frame, nil)
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## More Examples
|
||||
|
||||
### More Examples
|
||||
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
|
||||
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
|
||||
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
|
||||
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
|
||||
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
|
||||
|
||||
## Available Media Inputs
|
||||
|
||||
| Input | Linux | Mac | Windows |
|
||||
### Available Media Inputs
|
||||
| Input | Linux | Mac | Windows |
|
||||
| :--------: | :---: | :-: | :-----: |
|
||||
| Camera | ✔️ | ✔️ | ✔️ |
|
||||
| Microphone | ✔️ | ✔️ | ✔️ |
|
||||
| Screen | ✔️ | ✔️ | ✔️ |
|
||||
| Camera | ✔️ | ✔️ | ✔️ |
|
||||
| Microphone | ✔️ | ✔️ | ✔️ |
|
||||
| Screen | ✔️ | ✔️ | ✔️ |
|
||||
|
||||
By default, there's no media input registered. This decision was made to allow you to 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`:
|
||||
|
||||
```go
|
||||
@@ -130,9 +127,9 @@ Since `mediadevices` doesn't implement the video/audio codecs, it needs to call
|
||||
|
||||
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
|
||||
|
||||
### Video Codecs
|
||||
#### Video Codecs
|
||||
|
||||
#### x264
|
||||
##### x264
|
||||
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
|
||||
@@ -140,19 +137,19 @@ A free software library and application for encoding video streams into the H.26
|
||||
* Mac: `brew install x264`
|
||||
* Ubuntu: `apt install libx264-dev`
|
||||
|
||||
#### mmal
|
||||
##### mmal
|
||||
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
|
||||
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
|
||||
|
||||
#### openh264
|
||||
##### openh264
|
||||
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
|
||||
* Installation: no installation needed, included as a static binary
|
||||
|
||||
#### vpx
|
||||
##### vpx
|
||||
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
|
||||
@@ -160,7 +157,7 @@ A free software video codec library from Google and the Alliance for Open Media
|
||||
* Mac: `brew install libvpx`
|
||||
* Ubuntu: `apt install libvpx-dev`
|
||||
|
||||
#### vaapi
|
||||
##### vaapi
|
||||
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
|
||||
@@ -168,9 +165,9 @@ An open source API that allows applications such as VLC media player or GStreame
|
||||
* Ubuntu: `apt install libva-dev`
|
||||
|
||||
|
||||
### Audio Codecs
|
||||
#### Audio Codecs
|
||||
|
||||
#### opus
|
||||
##### opus
|
||||
A totally open, royalty-free, highly versatile audio codec.
|
||||
|
||||
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
|
||||
@@ -178,16 +175,14 @@ A totally open, royalty-free, highly versatile audio codec.
|
||||
* Mac: `brew install opus`
|
||||
* Ubuntu: `apt install libopus-dev`
|
||||
|
||||
## Benchmark
|
||||
|
||||
### Benchmark
|
||||
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
|
||||
|
||||
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Failed to find the best driver that fits the constraints
|
||||
### FAQ
|
||||
|
||||
#### Failed to find the best driver that fits the constraints
|
||||
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
|
||||
|
||||
1. Open all registered drivers
|
||||
@@ -198,13 +193,14 @@ So, when `mediadevices` returns `failed to find the best driver that fits the co
|
||||
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
|
||||
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
|
||||
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
|
||||
* If trying to use `import _ github.com/pion/mediadevices/pkg/driver/screen` note that you will need to use `GetDisplayMedia` instead of `GetUserMedia`
|
||||
|
||||
### Failed to find vpx/x264/mmal/opus codecs
|
||||
|
||||
#### Failed to find vpx/x264/mmal/opus codecs
|
||||
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
|
||||
|
||||
If you see the following error message at compile time:
|
||||
```
|
||||
|
||||
```bash
|
||||
# pkg-config --cflags -- vpx
|
||||
Package vpx was not found in the pkg-config search path.
|
||||
Perhaps you should add the directory containing `vpx.pc'
|
||||
@@ -218,8 +214,10 @@ There are 2 common problems:
|
||||
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
|
||||
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
|
||||
|
||||
### Roadmap
|
||||
The library can be used with our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||
|
||||
## Community
|
||||
### Community
|
||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||
|
||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||
@@ -227,11 +225,8 @@ Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and imp
|
||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||
|
||||
## Contributing
|
||||
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
|
||||
### Contributing
|
||||
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
|
||||
|
||||
* [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
|
||||
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
|
||||
|
||||
## License
|
||||
### License
|
||||
MIT License - see [LICENSE](LICENSE) for full text
|
||||
|
@@ -3,9 +3,9 @@ module github.com/pion/mediadevices/examples
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/esimov/pigo v1.4.5
|
||||
github.com/esimov/pigo v1.4.6
|
||||
github.com/pion/mediadevices v0.0.0
|
||||
github.com/pion/webrtc/v3 v3.1.43
|
||||
github.com/pion/webrtc/v3 v3.2.29
|
||||
)
|
||||
|
||||
replace github.com/pion/mediadevices v0.0.0 => ../
|
||||
|
181
examples/go.sum
181
examples/go.sum
@@ -1,17 +1,17 @@
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/blackjack/webcam v0.5.0 h1:NImYpsAbWxglejopcQGQ3FClxhJZaBBgr5fV7nsGdvk=
|
||||
github.com/blackjack/webcam v0.5.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg=
|
||||
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
|
||||
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
|
||||
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gen2brain/malgo v0.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo=
|
||||
github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
|
||||
github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -27,11 +27,12 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -48,88 +49,102 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/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/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
||||
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.43 h1:YT3ZTO94UT4kSBvZnRAH82+0jJPUruiKr9CEstdlQzk=
|
||||
github.com/pion/webrtc/v3 v3.1.43/go.mod h1:G/J8k0+grVsjC/rjCZ24AKoCCxcFFODgh7zThNZGs0M=
|
||||
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
||||
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||
github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
|
||||
github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
||||
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
|
||||
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/image v0.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-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -145,21 +160,44 @@ 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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -182,5 +220,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
1
examples/vnc/.gitignore
vendored
Normal file
1
examples/vnc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
vnc
|
46
go.mod
46
go.mod
@@ -1,17 +1,41 @@
|
||||
module github.com/pion/mediadevices
|
||||
|
||||
go 1.13
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
|
||||
github.com/gen2brain/malgo v0.10.35
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
|
||||
github.com/pion/interceptor v0.1.12
|
||||
github.com/blackjack/webcam v0.5.0
|
||||
github.com/gen2brain/malgo v0.11.21
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
|
||||
github.com/pion/interceptor v0.1.25
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/rtcp v1.2.10
|
||||
github.com/pion/rtp v1.7.13
|
||||
github.com/pion/webrtc/v3 v3.1.43
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
github.com/pion/rtcp v1.2.14
|
||||
github.com/pion/rtp v1.8.3
|
||||
github.com/pion/webrtc/v3 v3.2.29
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/image v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/ice/v2 v2.3.13 // indirect
|
||||
github.com/pion/mdns v0.0.12 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/sctp v1.8.12 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.8 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||
github.com/pion/stun v0.6.1 // indirect
|
||||
github.com/pion/transport/v2 v2.2.3 // indirect
|
||||
github.com/pion/turn/v2 v2.1.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
182
go.sum
182
go.sum
@@ -1,14 +1,14 @@
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/blackjack/webcam v0.5.0 h1:NImYpsAbWxglejopcQGQ3FClxhJZaBBgr5fV7nsGdvk=
|
||||
github.com/blackjack/webcam v0.5.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gen2brain/malgo v0.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo=
|
||||
github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
|
||||
github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -23,13 +23,14 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -47,86 +48,100 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/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/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
||||
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.43 h1:YT3ZTO94UT4kSBvZnRAH82+0jJPUruiKr9CEstdlQzk=
|
||||
github.com/pion/webrtc/v3 v3.1.43/go.mod h1:G/J8k0+grVsjC/rjCZ24AKoCCxcFFODgh7zThNZGs0M=
|
||||
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
||||
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||
github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
|
||||
github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
||||
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
|
||||
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -140,20 +155,42 @@ 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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -176,5 +213,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#define MAX_DEVICES 8
|
||||
#define MAX_PROPERTIES 64
|
||||
#define MAX_DEVICE_UID_CHARS 64
|
||||
#define MAX_DEVICE_NAME_CHARS 64
|
||||
|
||||
typedef const char* STATUS;
|
||||
static STATUS STATUS_OK = (STATUS) NULL;
|
||||
@@ -46,7 +47,7 @@ typedef enum AVBindFrameFormat {
|
||||
AVBindFrameFormatI420,
|
||||
AVBindFrameFormatNV21,
|
||||
AVBindFrameFormatNV12,
|
||||
AVBindFrameFormatYUY2,
|
||||
AVBindFrameFormatYUYV,
|
||||
AVBindFrameFormatUYVY,
|
||||
} AVBindFrameFormat;
|
||||
|
||||
@@ -65,6 +66,7 @@ typedef struct AVBindSession AVBindSession, *PAVBindSession;
|
||||
|
||||
typedef struct {
|
||||
char uid[MAX_DEVICE_UID_CHARS + 1];
|
||||
char name[MAX_DEVICE_NAME_CHARS + 1];
|
||||
} AVBindDevice, *PAVBindDevice;
|
||||
|
||||
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
||||
|
@@ -46,6 +46,8 @@
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static NSString *const UnrecognizedMacOSVersionException = @"UnrecognizedMacOSVersionException";
|
||||
|
||||
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
|
||||
@property (readonly) AVBindDataCallback mCallback;
|
||||
@@ -74,26 +76,61 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
|
||||
!CMSampleBufferIsValid(sampleBuffer) ||
|
||||
!CMSampleBufferDataIsReady(sampleBuffer)) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (imageBuffer == NULL) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
imageBuffer = CVBufferRetain(imageBuffer);
|
||||
CVBufferRetain(imageBuffer);
|
||||
CVReturn ret =
|
||||
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
if (ret != kCVReturnSuccess) {
|
||||
return;
|
||||
CVBufferRelease(imageBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle NV12 special case
|
||||
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
|
||||
if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
|
||||
// Get actual dimensions of image (without padding)
|
||||
size_t width = CVPixelBufferGetWidth(imageBuffer);
|
||||
size_t height = CVPixelBufferGetHeight(imageBuffer);
|
||||
size_t totalSize = /*Y plane*/ width * height + /*UV plane*/ width * height / 2;
|
||||
|
||||
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
|
||||
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
|
||||
|
||||
void *mergedBuffer = malloc(totalSize);
|
||||
if (!mergedBuffer) {
|
||||
NSLog(@"Failed to allocate memory for merged buffer");
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
CVBufferRelease(imageBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Truncate data where we know it should end to strip padding
|
||||
void *yPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
memcpy(mergedBuffer + row * width, yPlaneBuf + row * bytesPerRowY, width);
|
||||
}
|
||||
|
||||
void *uvPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
|
||||
for (size_t row = 0; row < height / 2; ++row) {
|
||||
memcpy(mergedBuffer + width * height + row * width, uvPlaneBuf + row * bytesPerRowUV, width);
|
||||
}
|
||||
|
||||
_mCallback(_mPUserData, mergedBuffer, (int)totalSize);
|
||||
free(mergedBuffer);
|
||||
} else {
|
||||
void *buf = CVPixelBufferGetBaseAddress(imageBuffer);
|
||||
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
|
||||
_mCallback(_mPUserData, buf, (int)dataSize);
|
||||
}
|
||||
|
||||
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
|
||||
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
|
||||
_mCallback(_mPUserData, buf, (int)dataSize);
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
CVBufferRelease(imageBuffer);
|
||||
}
|
||||
|
||||
@@ -139,7 +176,7 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
||||
case AVBindFrameFormatUYVY:
|
||||
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
||||
break;
|
||||
case AVBindFrameFormatYUY2:
|
||||
case AVBindFrameFormatYUYV:
|
||||
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
|
||||
break;
|
||||
// TODO: Add the rest of frame formats
|
||||
@@ -163,7 +200,7 @@ STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
||||
*pFormat = AVBindFrameFormatUYVY;
|
||||
break;
|
||||
case kCVPixelFormatType_422YpCbCr8_yuvs:
|
||||
*pFormat = AVBindFrameFormatYUY2;
|
||||
*pFormat = AVBindFrameFormatYUYV;
|
||||
break;
|
||||
// TODO: Add the rest of frame formats
|
||||
default:
|
||||
@@ -182,15 +219,32 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
|
||||
|
||||
PAVBindDevice pDevice;
|
||||
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
|
||||
NSArray *refAllTypes = @[
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||
AVCaptureDeviceTypeBuiltInMicrophone,
|
||||
AVCaptureDeviceTypeExternalUnknown
|
||||
];
|
||||
|
||||
NSArray *refAllTypes;
|
||||
#if defined(MAC_OS_VERSION_14_0)
|
||||
if (@available(macOS 14.0, *)) {
|
||||
refAllTypes = @[
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||
AVCaptureDeviceTypeMicrophone,
|
||||
AVCaptureDeviceTypeExternal,
|
||||
];
|
||||
} else {
|
||||
@throw [NSException exceptionWithName:UnrecognizedMacOSVersionException
|
||||
reason:@"Unrecognized or unsupported macOS version detected."
|
||||
userInfo:nil];
|
||||
}
|
||||
#else
|
||||
refAllTypes = @[
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||
AVCaptureDeviceTypeBuiltInMicrophone,
|
||||
AVCaptureDeviceTypeExternalUnknown,
|
||||
];
|
||||
#endif
|
||||
|
||||
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes: refAllTypes
|
||||
mediaType: _mediaType
|
||||
position: AVCaptureDevicePositionUnspecified];
|
||||
discoverySessionWithDeviceTypes: refAllTypes
|
||||
mediaType: _mediaType
|
||||
position: AVCaptureDevicePositionUnspecified];
|
||||
|
||||
int i = 0;
|
||||
for (AVCaptureDevice *refDevice in refSession.devices) {
|
||||
@@ -201,6 +255,8 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
|
||||
pDevice = devices + i;
|
||||
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
||||
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
||||
strncpy(pDevice->name, refDevice.localizedName.UTF8String, MAX_DEVICE_NAME_CHARS);
|
||||
pDevice->name[MAX_DEVICE_NAME_CHARS] = '\0';
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@@ -34,6 +34,7 @@ type Device struct {
|
||||
// UID is a unique identifier for a device
|
||||
UID string
|
||||
cDevice C.AVBindDevice
|
||||
Name string
|
||||
}
|
||||
|
||||
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
||||
@@ -44,8 +45,8 @@ func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
||||
return C.AVBindFrameFormatNV21, true
|
||||
case frame.FormatNV12:
|
||||
return C.AVBindFrameFormatNV12, true
|
||||
case frame.FormatYUY2:
|
||||
return C.AVBindFrameFormatYUY2, true
|
||||
case frame.FormatYUYV:
|
||||
return C.AVBindFrameFormatYUYV, true
|
||||
case frame.FormatUYVY:
|
||||
return C.AVBindFrameFormatUYVY, true
|
||||
default:
|
||||
@@ -61,8 +62,8 @@ func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
|
||||
return frame.FormatNV21, true
|
||||
case C.AVBindFrameFormatNV12:
|
||||
return frame.FormatNV12, true
|
||||
case C.AVBindFrameFormatYUY2:
|
||||
return frame.FormatYUY2, true
|
||||
case C.AVBindFrameFormatYUYV:
|
||||
return frame.FormatYUYV, true
|
||||
case C.AVBindFrameFormatUYVY:
|
||||
return frame.FormatUYVY, true
|
||||
default:
|
||||
@@ -87,6 +88,7 @@ func Devices(mediaType MediaType) ([]Device, error) {
|
||||
for i := range devices {
|
||||
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
||||
devices[i].cDevice = cDevices[i]
|
||||
devices[i].Name = C.GoString(&cDevices[i].name[0])
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
|
@@ -64,10 +64,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
imgReal := img.(*image.YCbCr)
|
||||
var y, cb, cr C.Slice
|
||||
y.data = (*C.uchar)(&imgReal.Y[0])
|
||||
|
@@ -65,10 +65,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
yuvImg := img.(*image.YCbCr)
|
||||
bounds := yuvImg.Bounds()
|
||||
|
@@ -62,6 +62,7 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
@@ -100,6 +101,8 @@ type encoderVP8 struct {
|
||||
|
||||
rate *framerateDetector
|
||||
|
||||
forceKeyFrame atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
@@ -304,10 +307,11 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
yuvImg := img.(*image.YCbCr)
|
||||
|
||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||
@@ -315,7 +319,7 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
||||
|
||||
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
||||
|
||||
if kf {
|
||||
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
|
||||
// Key frame
|
||||
C.setForceKFFlagVP8(&e.picParam, 1)
|
||||
C.setFrameTypeFlagVP8(&e.picParam, 0)
|
||||
@@ -545,6 +549,10 @@ func (e *encoderVP8) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoderVP8) ForceKeyFrame() {
|
||||
e.forceKeyFrame.Store(true)
|
||||
}
|
||||
|
||||
func (e *encoderVP8) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
@@ -18,8 +18,6 @@ func TestVP8ShouldImplementBitRateControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVP8ShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoderVP8{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
|
@@ -45,6 +45,7 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
@@ -92,6 +93,8 @@ type encoderVP9 struct {
|
||||
|
||||
rate *framerateDetector
|
||||
|
||||
forceKeyFrame atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
@@ -293,10 +296,11 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
yuvImg := img.(*image.YCbCr)
|
||||
|
||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||
@@ -304,7 +308,7 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
||||
|
||||
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
||||
|
||||
if kf {
|
||||
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
|
||||
C.setForceKFFlag9(&e.picParam, 1)
|
||||
C.setFrameTypeFlagVP9(&e.picParam, 0)
|
||||
e.picParam.refresh_frame_flags = 0
|
||||
@@ -480,6 +484,10 @@ func (e *encoderVP9) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoderVP9) ForceKeyFrame() {
|
||||
e.forceKeyFrame.Store(true)
|
||||
}
|
||||
|
||||
func (e *encoderVP9) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
@@ -18,8 +18,6 @@ func TestVP9ShouldImplementBitRateControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVP9ShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoderVP9{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
|
@@ -219,10 +219,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
yuvImg := img.(*image.YCbCr)
|
||||
bounds := yuvImg.Bounds()
|
||||
height := C.int(bounds.Dy())
|
||||
|
@@ -102,10 +102,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
img, _, err := e.r.Read()
|
||||
img, release, err := e.r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
defer release()
|
||||
yuvImg := img.(*image.YCbCr)
|
||||
|
||||
var rc C.int
|
||||
|
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
|
||||
}
|
@@ -17,6 +17,11 @@ type camera struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
devices, err := avfoundation.Devices(avfoundation.Video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -27,6 +32,7 @@ func init() {
|
||||
driver.GetManager().Register(cam, driver.Info{
|
||||
Label: device.UID,
|
||||
DeviceType: driver.Camera,
|
||||
Name: device.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -8,10 +8,15 @@ import (
|
||||
"errors"
|
||||
"image"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||
|
||||
"github.com/blackjack/webcam"
|
||||
"github.com/pion/mediadevices/pkg/driver"
|
||||
@@ -60,6 +65,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
const bufCount = 2
|
||||
|
||||
// Camera implementation using v4l2
|
||||
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
||||
type camera struct {
|
||||
@@ -70,10 +77,23 @@ type camera struct {
|
||||
started bool
|
||||
mutex sync.Mutex
|
||||
cancel func()
|
||||
prevFrameTime time.Time
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
// Clear all registered camera devices to prevent duplicates.
|
||||
// If first initalize call, this will be a noop.
|
||||
manager := driver.GetManager()
|
||||
for _, d := range manager.Query(driver.FilterVideoRecorder()) {
|
||||
manager.Delete(d.ID())
|
||||
}
|
||||
discovered := make(map[string]struct{})
|
||||
discover(discovered, "/dev/v4l/by-id/*")
|
||||
discover(discovered, "/dev/v4l/by-path/*")
|
||||
discover(discovered, "/dev/video*")
|
||||
}
|
||||
@@ -102,7 +122,21 @@ func discover(discovered map[string]struct{}, pattern string) {
|
||||
if reallink == prioritizedDevice {
|
||||
priority = driver.PriorityHigh
|
||||
}
|
||||
|
||||
var name, busInfo string
|
||||
if webcamCam, err := webcam.Open(cam.path); err == nil {
|
||||
name, _ = webcamCam.GetName()
|
||||
busInfo, _ = webcamCam.GetBusInfo()
|
||||
}
|
||||
|
||||
driver.GetManager().Register(cam, driver.Info{
|
||||
// Source: https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-querycap.html
|
||||
// Name of the device, a NUL-terminated UTF-8 string. For example: “Yoyodyne TV/FM”. One driver may support
|
||||
// different brands or models of video hardware. This information is intended for users, for example in a
|
||||
// menu of available devices. Since multiple TV cards of the same brand may be installed which are
|
||||
// supported by the same driver, this name should be combined with the character device file name
|
||||
// (e.g. /dev/video2) or the bus_info string to avoid ambiguities.
|
||||
Name: name + LabelSeparator + busInfo,
|
||||
Label: label + LabelSeparator + reallink,
|
||||
DeviceType: driver.Camera,
|
||||
Priority: priority,
|
||||
@@ -153,8 +187,13 @@ func (c *camera) Open() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Late frames should be discarded. Buffering should be handled in higher level.
|
||||
cam.SetBufferCount(1)
|
||||
// Buffering should be handled in higher level.
|
||||
err = cam.SetBufferCount(bufCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.prevFrameTime = time.Now()
|
||||
c.cam = cam
|
||||
return nil
|
||||
}
|
||||
@@ -223,6 +262,13 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
||||
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)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
@@ -239,6 +285,10 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
|
||||
if p.DiscardFramesOlderThan != 0 {
|
||||
c.prevFrameTime = time.Now()
|
||||
}
|
||||
|
||||
// Frame is empty.
|
||||
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
|
||||
if len(b) == 0 {
|
||||
@@ -272,13 +322,31 @@ func (c *camera) Properties() []prop.Media {
|
||||
}
|
||||
|
||||
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: int(frameSize.MaxWidth),
|
||||
Height: int(frameSize.MaxHeight),
|
||||
FrameFormat: supportedFormat,
|
||||
},
|
||||
})
|
||||
framerates := c.cam.GetSupportedFramerates(format, uint32(frameSize.MaxWidth), uint32(frameSize.MaxHeight))
|
||||
// If the camera doesn't support framerate, we just add the resolution and format
|
||||
if len(framerates) == 0 {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: int(frameSize.MaxWidth),
|
||||
Height: int(frameSize.MaxHeight),
|
||||
FrameFormat: supportedFormat,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for _, framerate := range framerates {
|
||||
for _, fps := range enumFramerate(framerate) {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: int(frameSize.MaxWidth),
|
||||
Height: int(frameSize.MaxHeight),
|
||||
FrameFormat: supportedFormat,
|
||||
FrameRate: fps,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
|
||||
for _, supportedResolution := range supportedResolutions {
|
||||
@@ -297,16 +365,100 @@ func (c *camera) Properties() []prop.Media {
|
||||
continue
|
||||
}
|
||||
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: width,
|
||||
Height: height,
|
||||
FrameFormat: supportedFormat,
|
||||
},
|
||||
})
|
||||
framerates := c.cam.GetSupportedFramerates(format, uint32(width), uint32(height))
|
||||
if len(framerates) == 0 {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: width,
|
||||
Height: height,
|
||||
FrameFormat: supportedFormat,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for _, framerate := range framerates {
|
||||
for _, fps := range enumFramerate(framerate) {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: width,
|
||||
Height: height,
|
||||
FrameFormat: supportedFormat,
|
||||
FrameRate: fps,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
func (c *camera) IsAvailable() (bool, error) {
|
||||
var err error
|
||||
|
||||
// close the opened file descriptor as quickly as possible and in all cases, including panics
|
||||
func() {
|
||||
var cam *webcam.Webcam
|
||||
if cam, err = webcam.Open(c.path); err == nil {
|
||||
defer cam.Close()
|
||||
var index int32
|
||||
// "Drivers must implement all the input ioctls when the device has one or more inputs..."
|
||||
// Source: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/video.html?highlight=vidioc_enuminput
|
||||
if index, err = cam.GetInput(); err == nil {
|
||||
err = cam.SelectInput(uint32(index))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var errno syscall.Errno
|
||||
errors.As(err, &errno)
|
||||
|
||||
// See https://man7.org/linux/man-pages/man3/errno.3.html
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
case errno == syscall.EBUSY:
|
||||
return false, availability.ErrBusy
|
||||
case errno == syscall.ENODEV || errno == syscall.ENOENT:
|
||||
return false, availability.ErrNoDevice
|
||||
default:
|
||||
return false, availability.NewError(errno.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// enumFramerate returns a list of fps options from a FrameRate struct.
|
||||
// discrete framerates will return a list of 1 fps element.
|
||||
// stepwise framerates will return a list of all possible fps options.
|
||||
func enumFramerate(framerate webcam.FrameRate) []float32 {
|
||||
var framerates []float32
|
||||
if framerate.StepNumerator == 0 && framerate.StepDenominator == 0 {
|
||||
fr, err := calcFramerate(framerate.MaxNumerator, framerate.MaxDenominator)
|
||||
if err != nil {
|
||||
return framerates
|
||||
}
|
||||
framerates = append(framerates, fr)
|
||||
} else {
|
||||
for n := framerate.MinNumerator; n <= framerate.MaxNumerator; n += framerate.StepNumerator {
|
||||
for d := framerate.MinDenominator; d <= framerate.MaxDenominator; d += framerate.StepDenominator {
|
||||
fr, err := calcFramerate(n, d)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
framerates = append(framerates, fr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return framerates
|
||||
}
|
||||
|
||||
// calcFramerate turns fraction into a float32 fps value.
|
||||
func calcFramerate(numerator uint32, denominator uint32) (float32, error) {
|
||||
if denominator == 0 {
|
||||
return 0, errors.New("framerate denominator is zero")
|
||||
}
|
||||
// round to three decimal places to avoid floating point precision issues
|
||||
return float32(math.Round(1000.0/((float64(numerator))/float64(denominator))) / 1000), nil
|
||||
}
|
||||
|
@@ -57,6 +57,68 @@ func TestDiscover(t *testing.T) {
|
||||
drvs[0].Info().Label,
|
||||
drvs[1].Info().Label,
|
||||
}
|
||||
|
||||
// Returned drivers are unordered. Sort to get static result.
|
||||
sort.Sort(sort.StringSlice(labels))
|
||||
|
||||
expected := longName + LabelSeparator + shortName
|
||||
if label := labels[0]; label != expected {
|
||||
t.Errorf("Expected label: %s, got: %s", expected, label)
|
||||
}
|
||||
|
||||
expectedNoLink := shortName2 + LabelSeparator + shortName2
|
||||
if label := labels[1]; label != expectedNoLink {
|
||||
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverByID(t *testing.T) {
|
||||
const (
|
||||
shortName = "id-unittest-video0"
|
||||
shortName2 = "id-unittest-video1"
|
||||
longName = "id-unittest-long-device-name:0:1:2:3"
|
||||
)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
byIdDir := filepath.Join(dir, "v4l", "by-id")
|
||||
if err := os.MkdirAll(byIdDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(
|
||||
filepath.Join(dir, shortName),
|
||||
filepath.Join(byIdDir, longName),
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
discovered := make(map[string]struct{})
|
||||
discover(discovered, filepath.Join(byIdDir, "*"))
|
||||
discover(discovered, filepath.Join(dir, "id-unittest-video*"))
|
||||
|
||||
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
|
||||
// Ignore real cameras.
|
||||
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "id-unittest")
|
||||
})
|
||||
if len(drvs) != 2 {
|
||||
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
|
||||
}
|
||||
|
||||
labels := []string{
|
||||
drvs[0].Info().Label,
|
||||
drvs[1].Info().Label,
|
||||
}
|
||||
|
||||
// Returned drivers are unordered. Sort to get static result.
|
||||
sort.Sort(sort.StringSlice(labels))
|
||||
|
||||
@@ -98,3 +160,34 @@ func TestGetCameraReadTimeout(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@@ -76,6 +76,7 @@ int listCamera(cameraList* list, const char** errstr)
|
||||
{
|
||||
list->name[i] = getCameraName(moniker);
|
||||
moniker->Release();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,11 @@ type camera struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
|
||||
|
||||
var list C.cameraList
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
@@ -30,6 +31,7 @@ type Info struct {
|
||||
Label string
|
||||
DeviceType DeviceType
|
||||
Priority Priority
|
||||
Name string
|
||||
}
|
||||
|
||||
type Adapter interface {
|
||||
@@ -44,3 +46,14 @@ type Driver interface {
|
||||
Info() Info
|
||||
Status() State
|
||||
}
|
||||
|
||||
type AvailabilityAdapter interface {
|
||||
IsAvailable() (bool, error)
|
||||
}
|
||||
|
||||
func IsAvailable(d Driver) (bool, error) {
|
||||
if aa, ok := d.(AvailabilityAdapter); ok {
|
||||
return aa.IsAvailable()
|
||||
}
|
||||
return false, availability.ErrUnimplemented
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package driver
|
||||
|
||||
import "sync"
|
||||
|
||||
// FilterFn is being used to decide if a driver should be included in the
|
||||
// query result.
|
||||
type FilterFn func(Driver) bool
|
||||
@@ -55,6 +57,7 @@ func FilterNot(filter FilterFn) FilterFn {
|
||||
|
||||
// Manager is a singleton to manage multiple drivers and their states
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
drivers map[string]Driver
|
||||
}
|
||||
|
||||
@@ -69,6 +72,8 @@ func GetManager() *Manager {
|
||||
|
||||
// Register registers adapter to be discoverable by Query
|
||||
func (m *Manager) Register(a Adapter, info Info) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
d := wrapAdapter(a, info)
|
||||
m.drivers[d.ID()] = d
|
||||
return nil
|
||||
@@ -76,6 +81,8 @@ func (m *Manager) Register(a Adapter, info Info) error {
|
||||
|
||||
// Query queries by using f to filter drivers, and simply return the filtered results.
|
||||
func (m *Manager) Query(f FilterFn) []Driver {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
results := make([]Driver, 0)
|
||||
for _, d := range m.drivers {
|
||||
if ok := f(d); ok {
|
||||
@@ -85,3 +92,10 @@ func (m *Manager) Query(f FilterFn) []Driver {
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Delete deletes a driver from manager given its ID
|
||||
func (m *Manager) Delete(id string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.drivers, id)
|
||||
}
|
||||
|
@@ -1,13 +1,17 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func filterTrue(d Driver) bool {
|
||||
func filterTrue(_ Driver) bool {
|
||||
return true
|
||||
}
|
||||
func filterFalse(d Driver) bool {
|
||||
func filterFalse(_ Driver) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -40,3 +44,71 @@ func TestFilterAnd(t *testing.T) {
|
||||
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeVideoAdapter struct{}
|
||||
|
||||
func (a *fakeVideoAdapter) Open() error { return nil }
|
||||
func (a *fakeVideoAdapter) Close() error { return nil }
|
||||
func (a *fakeVideoAdapter) Properties() []prop.Media { return nil }
|
||||
|
||||
func (a *fakeVideoAdapter) VideoRecord(_ prop.Media) (r video.Reader, err error) { return nil, nil }
|
||||
|
||||
type fakeAudioAdapter struct{}
|
||||
|
||||
func (a *fakeAudioAdapter) Open() error { return nil }
|
||||
func (a *fakeAudioAdapter) Close() error { return nil }
|
||||
func (a *fakeAudioAdapter) Properties() []prop.Media { return nil }
|
||||
|
||||
func (a *fakeAudioAdapter) AudioRecord(_ prop.Media) (r audio.Reader, err error) { return nil, nil }
|
||||
|
||||
type fakeAdapter struct{}
|
||||
|
||||
func (a *fakeAdapter) Open() error { return nil }
|
||||
func (a *fakeAdapter) Close() error { return nil }
|
||||
func (a *fakeAdapter) Properties() []prop.Media { return nil }
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
m := GetManager()
|
||||
|
||||
va := &fakeVideoAdapter{}
|
||||
err := m.Register(va, Info{})
|
||||
assert.NoError(t, err, "cannot register video adapter")
|
||||
assert.Equal(t, len(m.Query(filterTrue)), 1)
|
||||
|
||||
aa := &fakeAudioAdapter{}
|
||||
err = m.Register(aa, Info{})
|
||||
assert.NoError(t, err, "cannot register audio adapter")
|
||||
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||
|
||||
a := &fakeAdapter{}
|
||||
assert.Panics(t, func() { m.Register(a, Info{}) }, "should not register adapter that is neither audio nor video")
|
||||
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||
}
|
||||
|
||||
func TestRegisterSync(t *testing.T) {
|
||||
m := GetManager()
|
||||
start := make(chan struct{})
|
||||
race := func() {
|
||||
<-start
|
||||
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||
}
|
||||
|
||||
go race()
|
||||
go race()
|
||||
close(start)
|
||||
}
|
||||
|
||||
func TestQuerySync(t *testing.T) {
|
||||
m := GetManager()
|
||||
start := make(chan struct{})
|
||||
race := func() {
|
||||
<-start
|
||||
m.Query(filterTrue)
|
||||
}
|
||||
|
||||
go race()
|
||||
go race()
|
||||
close(start)
|
||||
// write while reading
|
||||
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||
}
|
||||
|
@@ -39,6 +39,11 @@ type microphone struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers active playback or capture devices. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
var err error
|
||||
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
|
||||
logger.Debugf("%v\n", message)
|
||||
@@ -63,6 +68,7 @@ func init() {
|
||||
Label: device.ID.String(),
|
||||
DeviceType: driver.Microphone,
|
||||
Priority: priority,
|
||||
Name: info.Name(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -191,36 +197,39 @@ func (m *microphone) Properties() []prop.Media {
|
||||
isBigEndian = true
|
||||
}
|
||||
|
||||
for ch := m.MinChannels; ch <= m.MaxChannels; ch++ {
|
||||
for _, format := range m.Formats {
|
||||
// FIXME: Currently support 48kHz only. We need to implement a resampler first.
|
||||
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
|
||||
sampleRate := 48000
|
||||
for i := 0; i < int(m.FormatCount); i++ {
|
||||
format := m.Formats[i]
|
||||
|
||||
supportedProp := prop.Media{
|
||||
Audio: prop.Audio{
|
||||
ChannelCount: int(ch),
|
||||
SampleRate: int(sampleRate),
|
||||
IsBigEndian: isBigEndian,
|
||||
// miniaudio only supports interleaved at the moment
|
||||
IsInterleaved: true,
|
||||
// FIXME: should change this to a less discrete value
|
||||
Latency: time.Millisecond * 20,
|
||||
},
|
||||
}
|
||||
|
||||
switch malgo.FormatType(format) {
|
||||
case malgo.FormatF32:
|
||||
supportedProp.SampleSize = 4
|
||||
supportedProp.IsFloat = true
|
||||
case malgo.FormatS16:
|
||||
supportedProp.SampleSize = 2
|
||||
supportedProp.IsFloat = false
|
||||
}
|
||||
|
||||
supportedProps = append(supportedProps, supportedProp)
|
||||
supportedProp := prop.Media{
|
||||
Audio: prop.Audio{
|
||||
ChannelCount: int(format.Channels),
|
||||
SampleRate: int(sampleRate),
|
||||
IsBigEndian: isBigEndian,
|
||||
// miniaudio only supports interleaved at the moment
|
||||
IsInterleaved: true,
|
||||
// FIXME: should change this to a less discrete value
|
||||
Latency: time.Millisecond * 20,
|
||||
},
|
||||
}
|
||||
|
||||
supportedFormat := true
|
||||
switch malgo.FormatType(format.Format) {
|
||||
case malgo.FormatF32:
|
||||
supportedProp.SampleSize = 4
|
||||
supportedProp.IsFloat = true
|
||||
case malgo.FormatS16:
|
||||
supportedProp.SampleSize = 2
|
||||
supportedProp.IsFloat = false
|
||||
default:
|
||||
supportedFormat = false
|
||||
}
|
||||
|
||||
if !supportedFormat {
|
||||
logger.Warnf("format '%s' not supported", format.Format)
|
||||
continue
|
||||
}
|
||||
supportedProps = append(supportedProps, supportedProp)
|
||||
// }
|
||||
}
|
||||
return supportedProps
|
||||
|
@@ -20,6 +20,11 @@ type screen struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers active displays. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
activeDisplays := screenshot.NumActiveDisplays()
|
||||
for i := 0; i < activeDisplays; i++ {
|
||||
priority := driver.PriorityNormal
|
||||
|
@@ -22,6 +22,11 @@ func deviceID(num int) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Initialize finds and registers active displays. This is part of an experimental API.
|
||||
func Initialize() {
|
||||
dp, err := openDisplay()
|
||||
if err != nil {
|
||||
// No x11 display available.
|
||||
|
@@ -3,7 +3,13 @@
|
||||
go-vnc is a VNC library for Go, initially supporting VNC clients but
|
||||
with the goal of eventually implementing a VNC server.
|
||||
|
||||
This library implements [RFC 6143](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
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package driver
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
@@ -21,6 +22,10 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
||||
state: StateClosed,
|
||||
}
|
||||
|
||||
if aa, ok := a.(AvailabilityAdapter); ok {
|
||||
d.isAvailable = aa.IsAvailable
|
||||
}
|
||||
|
||||
switch v := a.(type) {
|
||||
case VideoRecorder:
|
||||
// Only expose Driver and VideoRecorder interfaces
|
||||
@@ -28,7 +33,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
||||
r := &struct {
|
||||
Driver
|
||||
VideoRecorder
|
||||
}{d, d}
|
||||
AvailabilityAdapter
|
||||
}{d, d, d}
|
||||
return r
|
||||
case AudioRecorder:
|
||||
// Only expose Driver and AudioRecorder interfaces
|
||||
@@ -36,7 +42,8 @@ func wrapAdapter(a Adapter, info Info) Driver {
|
||||
return &struct {
|
||||
Driver
|
||||
AudioRecorder
|
||||
}{d, d}
|
||||
AvailabilityAdapter
|
||||
}{d, d, d}
|
||||
default:
|
||||
panic("adapter has to be either VideoRecorder/AudioRecorder")
|
||||
}
|
||||
@@ -46,9 +53,10 @@ type adapterWrapper struct {
|
||||
Adapter
|
||||
VideoRecorder
|
||||
AudioRecorder
|
||||
id string
|
||||
info Info
|
||||
state State
|
||||
id string
|
||||
info Info
|
||||
state State
|
||||
isAvailable func() (bool, error)
|
||||
}
|
||||
|
||||
func (w *adapterWrapper) ID() string {
|
||||
@@ -104,3 +112,10 @@ func (w *adapterWrapper) AudioRecord(p prop.Media) (r audio.Reader, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *adapterWrapper) IsAvailable() (bool, error) {
|
||||
if w.isAvailable == nil {
|
||||
return false, availability.ErrUnimplemented
|
||||
}
|
||||
return w.isAvailable()
|
||||
}
|
||||
|
@@ -39,6 +39,10 @@ func (a *audioAdapterBrokenMock) AudioRecord(p prop.Media) (r audio.Reader, err
|
||||
return nil, recordErr
|
||||
}
|
||||
|
||||
type availabilityAdapterMock struct{ videoAdapterMock }
|
||||
|
||||
func (a *availabilityAdapterMock) IsAvailable() (bool, error) { return true, nil }
|
||||
|
||||
func TestVideoWrapperState(t *testing.T) {
|
||||
var a videoAdapterMock
|
||||
d := wrapAdapter(&a, Info{})
|
||||
@@ -136,3 +140,38 @@ func TestAudioWrapperWithBrokenRecorderState(t *testing.T) {
|
||||
t.Errorf("expected the status to be %v, but got %v", StateClosed, d.Status())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapperAvailabilityAdapter(t *testing.T) {
|
||||
var aa availabilityAdapterMock
|
||||
d := wrapAdapter(&aa, Info{})
|
||||
|
||||
ok, err := IsAvailable(d)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil, but got %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("expected true, but got %v", ok)
|
||||
}
|
||||
|
||||
var v videoAdapterMock
|
||||
d = wrapAdapter(&v, Info{})
|
||||
|
||||
ok, err = IsAvailable(d)
|
||||
if err == nil {
|
||||
t.Errorf("expected err, but got %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("expected false, but got %v", ok)
|
||||
}
|
||||
|
||||
var a audioAdapterMock
|
||||
d = wrapAdapter(&a, Info{})
|
||||
|
||||
ok, err = IsAvailable(d)
|
||||
if err == nil {
|
||||
t.Errorf("expected err, but got %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("expected false, but got %v", ok)
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,86 @@ package frame
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
)
|
||||
|
||||
/*
|
||||
Thank you to https://github.com/filiptc/gorbit/blob/fa87ff39b68a6706306f34c318e0b9a5a3c97110/image/overlay.go#L37-L40
|
||||
for addMotionDht, dhtMarker, dht, and sosMarker. These are protected under the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Philip Thomas Casado
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
var (
|
||||
dhtMarker = []byte{255, 196}
|
||||
dht = []byte{1, 162, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250}
|
||||
sosMarker = []byte{255, 218}
|
||||
huffmanTableInfoLength = len(dhtMarker) + len(dht) + len(sosMarker)
|
||||
uninitializedHuffmanTableError jpeg.FormatError = jpeg.FormatError("uninitialized Huffman table")
|
||||
)
|
||||
|
||||
func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) {
|
||||
img, err := jpeg.Decode(bytes.NewReader(frame))
|
||||
if err == nil {
|
||||
return img, func() {}, err
|
||||
}
|
||||
|
||||
if errors.As(err, &uninitializedHuffmanTableError) {
|
||||
if err.Error() == uninitializedHuffmanTableError.Error() {
|
||||
img, err = jpeg.Decode(bytes.NewReader(addMotionDht(frame)))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return img, func() {}, err
|
||||
}
|
||||
|
||||
func addMotionDht(frame []byte) []byte {
|
||||
jpegParts := bytes.Split(frame, sosMarker)
|
||||
if len(jpegParts) != 2 {
|
||||
return frame
|
||||
}
|
||||
correctedFrame := make([]byte, len(jpegParts[0])+huffmanTableInfoLength+len(jpegParts[1]))
|
||||
correctedFrameOffset := 0
|
||||
|
||||
copy(correctedFrame[correctedFrameOffset:], jpegParts[0])
|
||||
correctedFrameOffset += len(jpegParts[0])
|
||||
|
||||
copy(correctedFrame[correctedFrameOffset:], dhtMarker)
|
||||
correctedFrameOffset += len(dhtMarker)
|
||||
|
||||
copy(correctedFrame[correctedFrameOffset:], dht)
|
||||
correctedFrameOffset += len(dht)
|
||||
|
||||
copy(correctedFrame[correctedFrameOffset:], sosMarker)
|
||||
correctedFrameOffset += len(sosMarker)
|
||||
|
||||
copy(correctedFrame[correctedFrameOffset:], jpegParts[1])
|
||||
return correctedFrame
|
||||
}
|
||||
|
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
|
||||
|
||||
const (
|
||||
// FormatI420 https://www.fourcc.org/pixel-format/yuv-i420/
|
||||
// FormatI420 https://wiki.videolan.org/YUV#I420
|
||||
FormatI420 Format = "I420"
|
||||
// FormatI444 is a YUV format without sub-sampling
|
||||
FormatI444 Format = "I444"
|
||||
// FormatNV21 https://www.fourcc.org/pixel-format/yuv-nv21/
|
||||
// FormatNV21 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-nv12.html
|
||||
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"
|
||||
// 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"
|
||||
// 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"
|
||||
|
||||
// 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"
|
||||
|
||||
// FormatMJPEG https://www.fourcc.org/mjpg/
|
||||
// FormatMJPEG https://wiki.videolan.org/MJPEG
|
||||
FormatMJPEG = "MJPEG"
|
||||
|
||||
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
|
||||
FormatZ16 = "Z16"
|
||||
)
|
||||
|
||||
const FormatYUYV = FormatYUY2
|
||||
|
||||
var decoderMap = map[Format]decoderFunc{
|
||||
FormatI420: decodeI420,
|
||||
FormatNV21: decodeNV21,
|
||||
FormatNV12: decodeNV12,
|
||||
FormatYUY2: decodeYUY2,
|
||||
FormatYUYV: decodeYUY2,
|
||||
FormatUYVY: decodeUYVY,
|
||||
FormatMJPEG: decodeMJPEG,
|
||||
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"
|
||||
"image"
|
||||
"image/color"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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
|
||||
func ToI420(r Reader) Reader {
|
||||
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) {
|
||||
img, _, err := r.Read()
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
|
||||
var releaseFunc func() = func() {}
|
||||
|
||||
imageToYCbCr(&yuvImg, img)
|
||||
|
||||
// Covert pixel format to I420
|
||||
switch yuvImg.SubsampleRatio {
|
||||
case image.YCbCrSubsampleRatio420:
|
||||
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:
|
||||
yuvImg = i422ToI420(yuvImg)
|
||||
cLen := yuvImg.CStride * (yuvImg.Rect.Dy() / 2)
|
||||
dst := getSlice(cLen)
|
||||
yuvImg = i422ToI420(yuvImg, dst)
|
||||
releaseFunc = func() {
|
||||
bytesPool.Put(dst)
|
||||
}
|
||||
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.
|
||||
const hasCGOConvert = true
|
||||
|
||||
func i444ToI420(img image.YCbCr) image.YCbCr {
|
||||
func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||
h := img.Rect.Dy()
|
||||
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.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
||||
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
||||
@@ -31,10 +34,13 @@ func i444ToI420(img image.YCbCr) image.YCbCr {
|
||||
return img
|
||||
}
|
||||
|
||||
func i422ToI420(img image.YCbCr) image.YCbCr {
|
||||
func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||
h := img.Rect.Dy()
|
||||
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.uchar)(&cbDst[0]), (*C.uchar)(&crDst[0]),
|
||||
(*C.uchar)(&img.Cb[0]), (*C.uchar)(&img.Cr[0]),
|
||||
|
@@ -10,13 +10,16 @@ import (
|
||||
|
||||
const hasCGOConvert = false
|
||||
|
||||
func i444ToI420(img image.YCbCr) image.YCbCr {
|
||||
func i444ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||
h := img.Rect.Dy()
|
||||
addrSrc0 := 0
|
||||
addrSrc1 := img.CStride
|
||||
cLen := img.CStride * (h / 2)
|
||||
cLen := img.CStride * (h / 4)
|
||||
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 j := 0; j < img.CStride/2; j++ {
|
||||
@@ -40,19 +43,22 @@ func i444ToI420(img image.YCbCr) image.YCbCr {
|
||||
return img
|
||||
}
|
||||
|
||||
func i422ToI420(img image.YCbCr) image.YCbCr {
|
||||
func i422ToI420(img image.YCbCr, dst []uint8) image.YCbCr {
|
||||
h := img.Rect.Dy()
|
||||
addrSrc := 0
|
||||
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
|
||||
|
||||
for i := 0; i < h/2; i++ {
|
||||
for j := 0; j < img.CStride; j++ {
|
||||
cb := uint16(img.Cb[addrSrc]) + uint16(img.Cb[addrSrc+img.CStride])
|
||||
cr := uint16(img.Cr[addrSrc]) + uint16(img.Cr[addrSrc+img.CStride])
|
||||
cbDst[addrDst] = uint8(cb / 4)
|
||||
crDst[addrDst] = uint8(cr / 4)
|
||||
cbDst[addrDst] = uint8(cb / 2)
|
||||
crDst[addrDst] = uint8(cr / 2)
|
||||
addrSrc++
|
||||
addrDst++
|
||||
}
|
||||
|
@@ -147,10 +147,11 @@ func TestToI420(t *testing.T) {
|
||||
r := ToI420(ReaderFunc(func() (image.Image, func(), error) {
|
||||
return c.src, func() {}, nil
|
||||
}))
|
||||
out, _, err := r.Read()
|
||||
out, release, err := r.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer release()
|
||||
if !reflect.DeepEqual(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++ {
|
||||
_, _, err := r.Read()
|
||||
_, release, err := r.Read()
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
release()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -526,6 +526,9 @@ func TestScale(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestScaleFastBoxSampling(t *testing.T) {
|
||||
if !hasCGOConvert {
|
||||
t.Skip("Skip: nocgo implementation is not supported for FastBoxSampling")
|
||||
}
|
||||
cases := map[string]struct {
|
||||
src image.Image
|
||||
width, height int
|
||||
@@ -535,19 +538,19 @@ func TestScaleFastBoxSampling(t *testing.T) {
|
||||
src: &image.YCbCr{
|
||||
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||
Y: []uint8{
|
||||
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
||||
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10,
|
||||
0xF0, 0xF8, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
|
||||
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,
|
||||
},
|
||||
Cb: []uint8{
|
||||
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,
|
||||
},
|
||||
Cr: []uint8{
|
||||
@@ -565,24 +568,104 @@ func TestScaleFastBoxSampling(t *testing.T) {
|
||||
expected: &image.YCbCr{
|
||||
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||
Y: []uint8{
|
||||
0xF0, 0x80, 0x08, 0x00, 0x78, 0x80,
|
||||
0x08, 0x00, 0x20, 0x20, 0x20, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x40, 0x58, 0x18, 0x18, 0x18,
|
||||
0xF4, 0x10, 0x00, 0x00, 0xF0, 0x10,
|
||||
0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x00, 0x80, 0x30, 0x00, 0x30, 0x00,
|
||||
},
|
||||
Cb: []uint8{
|
||||
0x20, 0x50, 0x68,
|
||||
0x68, 0xB0, 0x88,
|
||||
0x20, 0x80, 0x50,
|
||||
0x80, 0xE0, 0x32,
|
||||
},
|
||||
Cr: []uint8{
|
||||
0xE0, 0xB0, 0x98,
|
||||
0xD0, 0x98, 0x80,
|
||||
0xE0, 0x80, 0xB0,
|
||||
0xF0, 0x40, 0xC0,
|
||||
},
|
||||
YStride: 6,
|
||||
CStride: 3,
|
||||
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 {
|
||||
|
@@ -29,7 +29,7 @@ void fastBoxSampling(
|
||||
const int sw, const int sh, const int sstride,
|
||||
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++)
|
||||
{
|
||||
@@ -39,13 +39,14 @@ void fastBoxSampling(
|
||||
uint32_t* tmp2 = &tmp[ty * dstride];
|
||||
for (int sx = 0; sx < sw * ch; sx += ch)
|
||||
{
|
||||
if (tx * sw < sx * dw)
|
||||
tx += ch;
|
||||
|
||||
for (int c = 0; c < ch; 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"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type MediaConstraints struct {
|
||||
DeviceID StringConstraint
|
||||
@@ -145,11 +145,10 @@ func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
|
||||
cmps.add(p.Width, o.Width)
|
||||
cmps.add(p.Height, o.Height)
|
||||
cmps.add(p.FrameFormat, o.FrameFormat)
|
||||
// The next line is comment out for now to not include framerate in the fitness function.
|
||||
// As camera.Properties does not have access to the list of available framerate at the moment,
|
||||
// no driver can be matched with a framerate constraint.
|
||||
// Note this also affect screen caputre as screen.Properties does not fill in the Framerate field.
|
||||
// cmps.add(p.FrameRate, o.FrameRate)
|
||||
// skip framerate if not available in media properties
|
||||
if o.FrameRate > 0.0 {
|
||||
cmps.add(p.FrameRate, o.FrameRate)
|
||||
}
|
||||
cmps.add(p.SampleRate, o.SampleRate)
|
||||
cmps.add(p.Latency, o.Latency)
|
||||
cmps.add(p.ChannelCount, o.ChannelCount)
|
||||
@@ -229,16 +228,18 @@ func (c *comparisons) fitnessDistance() (float64, bool) {
|
||||
|
||||
// VideoConstraints represents a video's constraints
|
||||
type VideoConstraints struct {
|
||||
Width, Height IntConstraint
|
||||
FrameRate FloatConstraint
|
||||
FrameFormat FrameFormatConstraint
|
||||
Width, Height IntConstraint
|
||||
FrameRate FloatConstraint
|
||||
FrameFormat FrameFormatConstraint
|
||||
DiscardFramesOlderThan time.Duration
|
||||
}
|
||||
|
||||
// Video represents a video's constraints
|
||||
type Video struct {
|
||||
Width, Height int
|
||||
FrameRate float32
|
||||
FrameFormat frame.Format
|
||||
Width, Height int
|
||||
FrameRate float32
|
||||
FrameFormat frame.Format
|
||||
DiscardFramesOlderThan time.Duration
|
||||
}
|
||||
|
||||
// AudioConstraints represents an audio's constraints
|
||||
|
@@ -85,6 +85,60 @@ func TestCompareMatch(t *testing.T) {
|
||||
}},
|
||||
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": {
|
||||
MediaConstraints{VideoConstraints: VideoConstraints{
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,12 @@
|
||||
{
|
||||
"packagePatterns": ["^golang.org/x/"],
|
||||
"schedule": ["on the first day of the month"]
|
||||
},
|
||||
{
|
||||
"description": "Disable updating minimum Go version: https://github.com/renovatebot/renovate/issues/16715",
|
||||
"matchManagers": ["gomod"],
|
||||
"matchDepTypes": ["golang"],
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"ignorePaths": [],
|
||||
|
20
track.go
20
track.go
@@ -83,6 +83,7 @@ type baseTrack struct {
|
||||
Source
|
||||
err error
|
||||
onErrorHandler func(error)
|
||||
errMu sync.Mutex
|
||||
mu sync.Mutex
|
||||
endOnce sync.Once
|
||||
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
|
||||
// error occurs, handler will get called with the error given to the parameter.
|
||||
func (track *baseTrack) OnEnded(handler func(error)) {
|
||||
track.mu.Lock()
|
||||
track.errMu.Lock()
|
||||
track.onErrorHandler = handler
|
||||
err := track.err
|
||||
track.mu.Unlock()
|
||||
track.errMu.Unlock()
|
||||
|
||||
if err != nil && handler != nil {
|
||||
// Already errored.
|
||||
@@ -144,10 +145,10 @@ func (track *baseTrack) OnEnded(handler func(error)) {
|
||||
|
||||
// onError is a callback when an error occurs
|
||||
func (track *baseTrack) onError(err error) {
|
||||
track.mu.Lock()
|
||||
track.errMu.Lock()
|
||||
track.err = err
|
||||
handler := track.onErrorHandler
|
||||
track.mu.Unlock()
|
||||
track.errMu.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
track.endOnce.Do(func() {
|
||||
@@ -161,7 +162,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
defer track.mu.Unlock()
|
||||
|
||||
signalCh := make(chan chan<- struct{})
|
||||
var stopRead chan struct{}
|
||||
stopRead := make(chan struct{})
|
||||
track.activePeerConnections[ctx.ID()] = signalCh
|
||||
|
||||
var encodedReader RTPReadCloser
|
||||
@@ -171,6 +172,14 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
for _, wantedCodec := range ctx.CodecParameters() {
|
||||
logger.Debugf("trying to build %s rtp reader", wantedCodec.MimeType)
|
||||
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 {
|
||||
selectedCodec = wantedCodec
|
||||
break
|
||||
@@ -223,7 +232,6 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
|
||||
keyFrameController, ok := encodedReader.Controller().(codec.KeyFrameController)
|
||||
if ok {
|
||||
stopRead = make(chan struct{})
|
||||
go track.rtcpReadLoop(ctx.RTCPReader(), keyFrameController, stopRead)
|
||||
}
|
||||
|
||||
|
@@ -2,14 +2,33 @@ package mediadevices
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/pion/interceptor"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
"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) {
|
||||
errExpected := errors.New("an error")
|
||||
|
||||
t.Run("ErrorAfterRegister", func(t *testing.T) {
|
||||
tr := &baseTrack{}
|
||||
@@ -54,6 +73,38 @@ func TestOnEnded(t *testing.T) {
|
||||
t.Error("Timeout")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrorDurringBind", func(t *testing.T) {
|
||||
tr := &DummyBindTrack{
|
||||
baseTrack: &baseTrack{
|
||||
activePeerConnections: make(map[string]chan<- chan<- struct{}),
|
||||
mu: sync.Mutex{},
|
||||
},
|
||||
}
|
||||
|
||||
called := make(chan error, 1)
|
||||
tr.OnEnded(func(err error) {
|
||||
called <- errExpected
|
||||
})
|
||||
|
||||
_, err := tr.Bind(&fakeTrackLocalContext{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-called:
|
||||
if err != errExpected {
|
||||
t.Errorf("Expected to receive error: %v, got: %v", errExpected, err)
|
||||
}
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Error("Timeout")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type fakeTrackLocalContext struct {
|
||||
webrtc.TrackLocalContext
|
||||
}
|
||||
|
||||
type fakeRTCPReader struct {
|
||||
|
Reference in New Issue
Block a user