Compare commits

...

45 Commits

Author SHA1 Message Date
Valentin Cocaud
f5e42ede9c 🔧 replace webrtc by fork 2022-07-01 17:43:00 +02:00
Valentin Cocaud
0a1b59cbc3 Allow to enable frame copy on a video track 2022-07-01 17:40:08 +02:00
Valentin Cocaud
6e80ae05a6 🔥 remove and ignore idea files 2022-07-01 17:39:49 +02:00
Valentin Cocaud
bb19442836 fix dependency errors 2022-07-01 17:31:45 +02:00
Valentin Cocaud
96d6c18c1f remove idea files 2022-07-01 17:29:56 +02:00
Valentin Cocaud
25525f97c0 Write test instead of type checking hack 2022-07-01 17:29:53 +02:00
Valentin Cocaud
916b1c483f Update pkg/codec/codec.go
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2022-07-01 17:26:55 +02:00
Valentin Cocaud
524bf8faad Add an interface for easier customization of the EncoderController
Making the ReadCloser an Controllable allows to uncouple
the controller implementation from the encoder.
This is not needed for the 2 codec controller already implemented (openh264 and vpx)
but is more future proof in case it required for other codecs.
2022-07-01 17:26:55 +02:00
Valentin Cocaud
8a2dfcd5ba Force key frame on PLI 2022-07-01 17:26:52 +02:00
renovate[bot]
8fce8a2bb5 Update golang.org/x/image digest to 41969df (#410)
Generated by Renovate Bot

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

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

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

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

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

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

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

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

* Fix color depth transform
2022-04-05 13:11:28 +08:00
renovate[bot]
5ba49e03e7 Update module github.com/pion/webrtc/v3 to v3.1.27 (#369)
Generated by Renovate Bot

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

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

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

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

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

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

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

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Atsushi Watanabe <atsushi.w@ieee.org>
2021-11-01 09:20:03 +09:00
Renovate Bot
f8d1f974cf Update github.com/kbinani/screenshot commit hash to 7d3a670
Generated by Renovate Bot
2021-09-05 09:42:41 +09:00
Renovate Bot
809f74cafc Update codecov/codecov-action action to v2
Generated by Renovate Bot
2021-09-05 09:41:13 +09:00
Renovate Bot
eb2db82766 Update module github.com/gen2brain/malgo to v0.10.35
Generated by Renovate Bot
2021-07-22 09:19:54 +09:00
Renovate Bot
b4c6eb5409 Update module github.com/google/uuid to v1.3.0
Generated by Renovate Bot
2021-07-22 09:11:07 +09:00
Will Forcey
b263026d52 Fixed Comment Error 2021-07-22 08:55:34 +09:00
Renovate Bot
070ab924f9 Update golang.org/x/image commit hash to a66eb64
Generated by Renovate Bot
2021-07-12 09:55:11 +09:00
62 changed files with 3106 additions and 191 deletions

View File

@@ -17,9 +17,9 @@ jobs:
name: Linux Go ${{ matrix.go }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
@@ -32,7 +32,7 @@ jobs:
libx264-dev
- name: Run Test Suite
run: make test
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
if: matrix.go == '1.16'
build-darwin:
runs-on: macos-latest
@@ -43,9 +43,9 @@ jobs:
name: Darwin Go ${{ matrix.go }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
@@ -62,9 +62,9 @@ jobs:
name: Check Licenses
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: '1.16'
- name: Installing go-licenses

View File

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

2
.gitignore vendored
View File

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

View File

@@ -16,7 +16,8 @@ supported_platforms := \
linux-arm64 \
linux-x64 \
windows-x64 \
darwin-x64
darwin-x64 \
darwin-arm64
cmd_build := build
cmd_test := test
examples_dir := examples
@@ -25,6 +26,7 @@ codec_list := $(shell ls $(codec_dir)/*/Makefile)
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms)))
pkgs_without_mmal := $(shell go list ./... | grep -v mmal)
pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
define BUILD_TEMPLATE
ifneq (,$$(findstring $(2)-$(3),$$(supported_platforms)))
@@ -74,7 +76,7 @@ $(cmd_test):
go vet $(pkgs_without_mmal)
go build $(pkgs_without_mmal)
# go build without CGO
CGO_ENABLED=0 go build . pkg/...
CGO_ENABLED=0 go build $(pkgs_without_cgo)
# go build with CGO
CGO_ENABLED=1 go build $(pkgs_without_mmal)
$(MAKE) --directory=$(examples_dir)

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM=
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -21,6 +22,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -36,18 +38,19 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg=
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10=
github.com/pion/ice/v2 v2.0.16 h1:K6bzD8ef9vMKbGMTHaUweHXEyuNGnvr2zdqKoLKZPn0=
github.com/pion/ice/v2 v2.0.16/go.mod h1:SJNJzC27gDZoOW0UoxIoC8Hf2PDxG28hQyNdSexDu38=
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
github.com/pion/ice/v2 v2.1.7 h1:FjgDfUNrVYTxQabJrkBX6ld12tvYbgzHenqPh3PJF6E=
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@@ -60,6 +63,8 @@ github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
@@ -69,17 +74,14 @@ github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
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.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pion/webrtc/v3 v3.0.20 h1:Jj0sk45MqQdkR24E1wbFRmOzb1Lv258ot9zd2fYB/Pw=
github.com/pion/webrtc/v3 v3.0.20/go.mod h1:0eJnCpQrUMpRnvyonw4ZiWClToerpixrZ2KcoTxvX9M=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -90,24 +92,32 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-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-20201202161906-c7110b5ffcbb/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-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -116,19 +126,23 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -147,5 +161,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.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=

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

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

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

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

17
go.mod
View File

@@ -2,14 +2,17 @@ module github.com/pion/mediadevices
go 1.13
replace github.com/pion/webrtc/v3 => github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f
require (
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
github.com/gen2brain/malgo v0.10.29
github.com/google/uuid v1.2.0
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0
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/logging v0.2.2
github.com/pion/rtp v1.6.5
github.com/pion/webrtc/v3 v3.0.29
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c
github.com/pion/rtcp v1.2.9
github.com/pion/rtp v1.7.13
github.com/pion/webrtc/v3 v3.1.34
golang.org/x/image v0.0.0-20220617043117-41969df76e82
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

110
go.sum
View File

@@ -1,14 +1,14 @@
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f h1:o7MCxR85nZxyOgjkmjtnXHsmPmchX3AEbWb/Bgpy3aI=
github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f/go.mod h1:mO/yv7fBN3Lp7YNlnYcTj1jtpvNvssJG+7eh6itZ4xM=
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM=
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@@ -19,15 +19,19 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0 h1:lICR2wyk9J6T709NawrhNTDi9DjMIbQqdlPT/EE0xBI=
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0/go.mod h1:ZceVWGtzUZmxyN+/1I+oG31oOm1dOA2QUNbua9TLVdE=
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/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=
@@ -40,50 +44,50 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
github.com/pion/ice/v2 v2.1.7 h1:FjgDfUNrVYTxQabJrkBX6ld12tvYbgzHenqPh3PJF6E=
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
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.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/ice/v2 v2.2.1 h1:R3MeuJZpU1ty3diPqpD5OxaxcZ15eprAc+EtUiSoFxg=
github.com/pion/ice/v2 v2.2.1/go.mod h1:Op8jlPtjeiycsXh93Cs4jK82C9j/kh7vef6ztIOvtIQ=
github.com/pion/interceptor v0.1.7 h1:HThW0tIIKT9RRoDWGURe8rlZVOx0fJHxBHpA0ej0+bo=
github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pion/webrtc/v3 v3.0.29 h1:pVs6mYjbbYvC8pMsztayEz35DnUEFLPswsicGXaQjxo=
github.com/pion/webrtc/v3 v3.0.29/go.mod h1:XFQeLYBf++bWWA0sJqh6zF1ouWluosxwTOMOoTZGaD0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
@@ -96,24 +100,23 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
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-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c h1:FRR4fGZm/CMwZka5baQ4z8c8StbxJOMjS/45e0BAxK0=
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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-20201202161906-c7110b5ffcbb/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-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
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 h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -128,13 +131,16 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -149,6 +155,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -1,5 +1,7 @@
package mediadevices
import "github.com/pion/mediadevices/pkg/codec"
type EncodedBuffer struct {
Data []byte
Samples uint32
@@ -8,11 +10,13 @@ type EncodedBuffer struct {
type EncodedReadCloser interface {
Read() (EncodedBuffer, func(), error)
Close() error
codec.Controllable
}
type encodedReadCloserImpl struct {
readFn func() (EncodedBuffer, func(), error)
closeFn func() error
readFn func() (EncodedBuffer, func(), error)
closeFn func() error
controllerFn func() codec.EncoderController
}
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
@@ -23,9 +27,14 @@ func (r *encodedReadCloserImpl) Close() error {
return r.closeFn()
}
func (r *encodedReadCloserImpl) Controller() codec.EncoderController {
return r.controllerFn()
}
type encodedIOReadCloserImpl struct {
readFn func([]byte) (int, error)
closeFn func() error
readFn func([]byte) (int, error)
closeFn func() error
controller func() codec.EncoderController
}
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
@@ -48,7 +57,8 @@ func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserIm
encoded.Data = encoded.Data[n:]
return n, nil
},
closeFn: reader.Close,
closeFn: reader.Close,
controller: reader.Controller,
}
}
@@ -59,3 +69,7 @@ func (r *encodedIOReadCloserImpl) Read(b []byte) (int, error) {
func (r *encodedIOReadCloserImpl) Close() error {
return r.closeFn()
}
func (r *encodedIOReadCloserImpl) Controller() codec.EncoderController {
return r.controller()
}

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ package avfoundation
// }
import "C"
import (
"context"
"fmt"
"io"
"unsafe"
@@ -93,9 +94,11 @@ func Devices(mediaType MediaType) ([]Device, error) {
// ReadCloser is a wrapper around the data callback from AVFoundation. The data received from the
// the underlying callback can be retrieved by calling Read.
type ReadCloser struct {
dataChan chan []byte
id handleID
onClose func()
dataChan chan []byte
id handleID
onClose func()
cancelCtx context.Context
cancelFunc func()
}
func newReadCloser(onClose func()) *ReadCloser {
@@ -103,12 +106,22 @@ func newReadCloser(onClose func()) *ReadCloser {
rc.dataChan = make(chan []byte, 1)
rc.onClose = onClose
rc.id = register(rc.dataCb)
cancelCtx, cancelFunc := context.WithCancel(context.Background())
rc.cancelCtx = cancelCtx
rc.cancelFunc = cancelFunc
return &rc
}
func (rc *ReadCloser) dataCb(data []byte) {
// TODO: add a policy for slow reader
rc.dataChan <- data
if rc.cancelCtx.Err() != nil {
return
}
select {
case <-rc.cancelCtx.Done():
close(rc.dataChan)
case rc.dataChan <- data:
}
}
// Read reads raw data, the format is determined by the media type and property:
@@ -127,7 +140,7 @@ func (rc *ReadCloser) Close() {
if rc.onClose != nil {
rc.onClose()
}
close(rc.dataChan)
rc.cancelFunc()
unregister(rc.id)
}

View File

@@ -112,15 +112,37 @@ type VideoEncoderBuilder interface {
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
}
// ReadCloser is an io.ReadCloser with methods for rate limiting: SetBitRate and ForceKeyFrame
// ReadCloser is an io.ReadCloser with a controller
type ReadCloser interface {
Read() (b []byte, release func(), err error)
Close() error
Controllable
}
// EncoderController is the interface allowing to control the encoder behaviour after it's initialisation.
// It will possibly have common control method in the future.
// A controller can have optional methods represented by *Controller interfaces
type EncoderController interface{}
// Controllable is a interface representing a encoder which can be controlled
// after it's initialisation with an EncoderController
type Controllable interface {
Controller() EncoderController
}
// KeyFrameController is a interface representing an encoder that can be forced to produce key frame on demand
type KeyFrameController interface {
EncoderController
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
// BitRateController is a interface representing an encoder which can have a variable bit rate
type BitRateController interface {
EncoderController
// SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
// but this also means that the quality will also be lower.
SetBitRate(int) error
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
ForceKeyFrame() error
}
// BaseParams represents an codec's encoding properties

View File

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

View File

@@ -91,12 +91,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {

View File

@@ -0,0 +1,65 @@
package mmal
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -81,16 +81,15 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
Slice payload = {0};
if(e->force_key_frame == 1) {
info.eFrameType = videoFrameTypeI;
e->engine->ForceIntraFrame(true);
e->force_key_frame = 0;
}
pic.iPicWidth = f.width;
pic.iPicHeight = f.height;
pic.iColorFormat = videoFormatI420;
// We always received I420 format
pic.iStride[0] = pic.iPicWidth;
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
pic.iStride[0] = f.ystride;
pic.iStride[1] = pic.iStride[2] = f.cstride;
pic.pData[0] = (unsigned char *)f.y;
pic.pData[1] = (unsigned char *)f.u;
pic.pData[2] = (unsigned char *)f.v;

View File

@@ -12,6 +12,8 @@ typedef struct Slice {
typedef struct Frame {
void *y, *u, *v;
int ystride;
int cstride;
int height;
int width;
} Frame;

Binary file not shown.

View File

@@ -65,11 +65,13 @@ func (e *encoder) Read() ([]byte, func(), error) {
bounds := yuvImg.Bounds()
var rv C.int
s := C.enc_encode(e.engine, C.Frame{
y: unsafe.Pointer(&yuvImg.Y[0]),
u: unsafe.Pointer(&yuvImg.Cb[0]),
v: unsafe.Pointer(&yuvImg.Cr[0]),
height: C.int(bounds.Max.Y - bounds.Min.Y),
width: C.int(bounds.Max.X - bounds.Min.X),
y: unsafe.Pointer(&yuvImg.Y[0]),
u: unsafe.Pointer(&yuvImg.Cb[0]),
v: unsafe.Pointer(&yuvImg.Cr[0]),
ystride: C.int(yuvImg.YStride),
cstride: C.int(yuvImg.CStride),
height: C.int(bounds.Max.Y - bounds.Min.Y),
width: C.int(bounds.Max.X - bounds.Min.X),
}, &rv)
if err := errResult(rv); err != nil {
return nil, func() {}, fmt.Errorf("failed in encoding: %v", err)
@@ -79,19 +81,23 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, nil
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error {
e.engine.force_key_frame = C.int(1)
return nil
}
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
var rv C.int

View File

@@ -8,5 +8,6 @@ package openh264
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-x64.a
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-x64.a
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-arm64.a
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-windows-x64.a -lssp
import "C"

View File

@@ -0,0 +1,63 @@
package openh264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}

Binary file not shown.

View File

@@ -121,11 +121,14 @@ func (e *encoder) SetBitRate(bitRate int) error {
return nil
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
if e.engine == nil {
return nil
}
C.opus_encoder_destroy(e.engine)
e.engine = nil
return nil

View File

@@ -8,5 +8,6 @@ package opus
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-arm64.a -lm
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-x64.a -lm
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-x64.a
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-arm64.a
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-windows-x64.a
import "C"

View File

@@ -0,0 +1,60 @@
package opus
import (
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
func TestShouldImplementBitRateControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderSimpleReadTest(t, &p,
prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
},
wave.NewInt16Interleaved(wave.ChunkInfo{
Len: 960,
SamplingRate: 48000,
Channels: 2,
}),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderCloseTwiceTest(t, &p, prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
})
})
}

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -540,18 +541,18 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoderVP8) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoderVP8) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoderVP8) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

View File

@@ -0,0 +1,27 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
import (
"github.com/pion/mediadevices/pkg/codec"
"testing"
)
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoderVP8{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoderVP8{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -475,18 +476,18 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoderVP9) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoderVP9) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoderVP9) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

View File

@@ -0,0 +1,22 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoderVP9{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control
e := &encoderVP9{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -295,10 +295,6 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
}
func (e *encoder) ForceKeyFrame() error {
e.mu.Lock()
defer e.mu.Unlock()
@@ -306,10 +302,18 @@ func (e *encoder) ForceKeyFrame() error {
return nil
}
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
C.free(unsafe.Pointer(e.raw))

View File

@@ -7,11 +7,63 @@ import (
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
p.LagInFrames = 0
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
})
}
}
func TestImageSizeChange(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
@@ -141,7 +193,7 @@ func TestRequestKeyFrame(t *testing.T) {
t.Fatal(err)
}
rel()
r.ForceKeyFrame()
r.Controller().(codec.KeyFrameController).ForceKeyFrame()
_, rel, err = r.Read()
if err != nil {
t.Fatal(err)
@@ -158,3 +210,19 @@ func TestRequestKeyFrame(t *testing.T) {
}
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

View File

@@ -18,6 +18,7 @@ typedef struct Encoder {
x264_t *h;
x264_picture_t pic_in;
x264_param_t param;
int force_key_frame;
} Encoder;
Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
@@ -85,8 +86,14 @@ Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
e->pic_in.img.plane[0] = y;
e->pic_in.img.plane[1] = cb;
e->pic_in.img.plane[2] = cr;
if (e->force_key_frame) {
e->pic_in.i_type = X264_TYPE_IDR;
} else {
e->pic_in.i_type = X264_TYPE_AUTO;
}
int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &pic_out);
e->force_key_frame = 0;
Slice s = {.data_len = frame_size};
if (frame_size <= 0) {
*rc = ERR_ENCODE;

View File

@@ -124,12 +124,13 @@ func (e *encoder) Read() ([]byte, func(), error) {
return encoded, func() {}, err
}
func (e *encoder) SetBitRate(b int) error {
panic("SetBitRate is not implemented")
func (e *encoder) ForceKeyFrame() error {
e.engine.force_key_frame = C.int(1)
return nil
}
func (e *encoder) ForceKeyFrame() error {
panic("ForceKeyFrame is not implemented")
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {

View File

@@ -0,0 +1,65 @@
package x264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}

View File

@@ -13,6 +13,7 @@ import (
type camera struct {
device avfoundation.Device
session *avfoundation.Session
rcClose func()
}
func init() {
@@ -43,6 +44,9 @@ func (cam *camera) Open() error {
}
func (cam *camera) Close() error {
if cam.rcClose != nil {
cam.rcClose()
}
return cam.session.Close()
}
@@ -56,6 +60,7 @@ func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) {
if err != nil {
return nil, err
}
cam.rcClose = rc.Close
r := video.ReaderFunc(func() (image.Image, func(), error) {
frame, _, err := rc.Read()
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"io"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/blackjack/webcam"
@@ -133,6 +134,19 @@ func newCamera(path string) *camera {
return c
}
func getCameraReadTimeout() uint32 {
// default to 5 seconds
var readTimeoutSec uint32 = 5
if val, ok := os.LookupEnv("PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"); ok {
if valInt, err := strconv.Atoi(val); err == nil {
if valInt > 0 {
readTimeoutSec = uint32(valInt)
}
}
}
return readTimeoutSec
}
func (c *camera) Open() error {
cam, err := webcam.Open(c.path)
if err != nil {
@@ -179,12 +193,21 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, err
}
if p.FrameRate > 0 {
err = c.cam.SetFramerate(float32(p.FrameRate))
if err != nil {
return nil, err
}
}
if err := c.cam.StartStreaming(); err != nil {
return nil, err
}
cam := c.cam
readTimeoutSec := getCameraReadTimeout()
ctx, cancel := context.WithCancel(context.Background())
c.cancel = cancel
var buf []byte
@@ -200,7 +223,7 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
return nil, func() {}, io.EOF
}
err := cam.WaitForFrame(5) // 5 seconds
err := cam.WaitForFrame(readTimeoutSec)
switch err.(type) {
case nil:
case *webcam.Timeout:

View File

@@ -70,3 +70,31 @@ func TestDiscover(t *testing.T) {
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
}
}
func TestGetCameraReadTimeout(t *testing.T) {
var expected uint32 = 5
value := getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
envVarName := "PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"
os.Setenv(envVarName, "text")
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
os.Setenv(envVarName, "-1")
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
os.Setenv(envVarName, "1")
expected = 1
value = getCameraReadTimeout()
if value != expected {
t.Errorf("Expected: %d, got: %d", expected, value)
}
}

View File

@@ -111,6 +111,8 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
config.PerformanceProfile = malgo.LowLatency
config.Capture.Channels = uint32(inputProp.ChannelCount)
config.SampleRate = uint32(inputProp.SampleRate)
//FIX: Turn on the microphone with the current device id
config.Capture.DeviceID = m.ID.Pointer()
if inputProp.SampleSize == 4 && inputProp.IsFloat {
config.Capture.Format = malgo.FormatF32
} else if inputProp.SampleSize == 2 && !inputProp.IsFloat {

View File

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

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -109,7 +109,7 @@ func Scale(width, height int, scaler Scaler) TransformFunc {
yDy := rect.Dy()
cRect := fixedRect(rect, i1.SubsampleRatio)
cDx := cRect.Dx()
cDy := cRect.Dx()
cDy := cRect.Dy()
yLen := yDx * yDy
cLen := cDx * cDy
if len(imgDst.Y) < yLen {

View File

@@ -36,6 +36,32 @@ func TestScale(t *testing.T) {
Rect: image.Rect(0, 0, 2, 2),
},
},
"RGBASameSize": {
src: &image.RGBA{
Pix: []uint8{
// R G B A | R G B A | R G B A | R G B A
0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF,
0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF,
},
Stride: 16,
Rect: image.Rect(0, 0, 4, 4),
},
width: 4,
height: 4,
expected: &image.RGBA{
Pix: []uint8{
// R G B A | R G B A | R G B A | R G B A
0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF,
0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF,
},
Stride: 16,
Rect: image.Rect(0, 0, 4, 4),
},
},
"I444": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio444,
@@ -91,6 +117,70 @@ func TestScale(t *testing.T) {
Rect: image.Rect(0, 0, 3, 3),
},
},
"I444SameSize": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio444,
Y: []uint8{
0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0,
0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x40, 0x40, 0x80, 0x80,
0x80, 0x80, 0x40, 0x40, 0x80, 0x80,
},
YStride: 6,
CStride: 6,
Rect: image.Rect(0, 0, 6, 6),
},
width: 6,
height: 6,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio444,
Y: []uint8{
0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0,
0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x40, 0x40, 0x80, 0x80,
0x80, 0x80, 0x40, 0x40, 0x80, 0x80,
},
YStride: 6,
CStride: 6,
Rect: image.Rect(0, 0, 6, 6),
},
},
"I422": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio422,
@@ -155,6 +245,82 @@ func TestScale(t *testing.T) {
Rect: image.Rect(0, 0, 4, 4),
},
},
"I422SameSize": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio422,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0xE0, 0xE0,
0x80, 0x80, 0xE0, 0xE0,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0xF0, 0xF0, 0x40, 0x40,
0xF0, 0xF0, 0x40, 0x40,
},
YStride: 8,
CStride: 4,
Rect: image.Rect(0, 0, 8, 8),
},
width: 8,
height: 8,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio422,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0xE0, 0xE0,
0x80, 0x80, 0xE0, 0xE0,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80,
0xF0, 0xF0, 0x40, 0x40,
0xF0, 0xF0, 0x40, 0x40,
},
YStride: 8,
CStride: 4,
Rect: image.Rect(0, 0, 8, 8),
},
},
"I420": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
@@ -207,6 +373,118 @@ func TestScale(t *testing.T) {
Rect: image.Rect(0, 0, 4, 4),
},
},
"I420SameSize": {
src: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80,
0x80, 0x80, 0xE0, 0xE0,
0x80, 0x80, 0xE0, 0xE0,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80,
0xF0, 0xF0, 0x40, 0x40,
0xF0, 0xF0, 0x40, 0x40,
},
YStride: 8,
CStride: 4,
Rect: image.Rect(0, 0, 8, 8),
},
width: 8,
height: 8,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00,
},
Cb: []uint8{
0x20, 0x20, 0x80, 0x80,
0x20, 0x20, 0x80, 0x80,
0x80, 0x80, 0xE0, 0xE0,
0x80, 0x80, 0xE0, 0xE0,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80,
0xE0, 0xE0, 0x80, 0x80,
0xF0, 0xF0, 0x40, 0x40,
0xF0, 0xF0, 0x40, 0x40,
},
YStride: 8,
CStride: 4,
Rect: image.Rect(0, 0, 8, 8),
},
},
"I420NonSquareImage": {
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,
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,
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, 0x30, 0x30,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80, 0xB0, 0xB0,
0xE0, 0xE0, 0x80, 0x80, 0xB0, 0xB0,
0xF0, 0xF0, 0x40, 0x40, 0xC0, 0xC0,
0xF0, 0xF0, 0x40, 0x40, 0xC0, 0xC0,
},
YStride: 12,
CStride: 6,
Rect: image.Rect(0, 0, 12, 8),
},
width: 6,
height: 4,
expected: &image.YCbCr{
SubsampleRatio: image.YCbCrSubsampleRatio420,
Y: []uint8{
0xF0, 0x10, 0x00, 0x00, 0xF0, 0x10,
0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x30, 0x00, 0x30, 0x00,
},
Cb: []uint8{
0x20, 0x80, 0x50,
0x80, 0xE0, 0x30,
},
Cr: []uint8{
0xE0, 0x80, 0xB0,
0xF0, 0x40, 0xC0,
},
YStride: 6,
CStride: 3,
Rect: image.Rect(0, 0, 6, 4),
},
},
}
for name, algo := range scalerTestAlgos {
algo := algo
@@ -247,6 +525,100 @@ func TestScale(t *testing.T) {
}
}
func TestScaleFastBoxSampling(t *testing.T) {
cases := map[string]struct {
src image.Image
width, height int
expected image.Image
}{
"I420NonSquareImage": {
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,
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,
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, 0x30, 0x30,
},
Cr: []uint8{
0xE0, 0xE0, 0x80, 0x80, 0xB0, 0xB0,
0xE0, 0xE0, 0x80, 0x80, 0xB0, 0xB0,
0xF0, 0xF0, 0x40, 0x40, 0xC0, 0xC0,
0xF0, 0xF0, 0x40, 0x40, 0xC0, 0xC0,
},
YStride: 12,
CStride: 6,
Rect: image.Rect(0, 0, 12, 8),
},
width: 6,
height: 4,
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,
},
Cb: []uint8{
0x20, 0x50, 0x68,
0x68, 0xB0, 0x88,
},
Cr: []uint8{
0xE0, 0xB0, 0x98,
0xD0, 0x98, 0x80,
},
YStride: 6,
CStride: 3,
Rect: image.Rect(0, 0, 6, 4),
},
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
trans := Scale(c.width, c.height, ScalerFastBoxSampling)
r := trans(ReaderFunc(func() (image.Image, func(), error) {
return c.src, func() {}, nil
}))
for i := 0; i < 4; i++ {
out, _, err := r.Read()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(c.expected, out) {
t.Errorf("Expected output image:\n%v\ngot:\n%v\nrepeat: %d", c.expected, out, i)
}
// Destroy output contents
switch v := out.(type) {
case *image.RGBA:
v.Stride = 10
v.Pix = v.Pix[:1]
v.Rect.Max.X = 1
case *image.YCbCr:
v.YStride = 10
v.CStride = 100
v.Y = v.Y[:1]
v.Cb = v.Cb[:2]
v.Cr = v.Cr[:1]
v.Rect.Max.X = 1
}
}
})
}
}
func BenchmarkScale(b *testing.B) {
for name, algo := range scalerBenchAlgos {
algo := algo

View File

@@ -29,7 +29,7 @@ void fastBoxSampling(
const int sw, const int sh, const int sstride,
uint32_t* tmp)
{
memset(tmp, 0, dw * dh * ch);
memset(tmp, 0, dw * dh * ch * sizeof(tmp[0]));
for (int sy = 0; sy < sh; sy++)
{

View File

@@ -30,10 +30,18 @@ func (p *rgbLikeYCbCr) At(x, y int) color.Color {
}
func (p *rgbLikeYCbCr) Set(x, y int, c color.Color) {
rgb := c.(*color.RGBA64)
p.y.SetGray(x, y, color.Gray{uint8(rgb.R / 0x100)})
if (image.Point{x, y}.In(p.cb.Rect)) {
p.cb.SetGray(x, y, color.Gray{uint8(rgb.G / 0x100)})
p.cr.SetGray(x, y, color.Gray{uint8(rgb.B / 0x100)})
switch v := c.(type) {
case color.RGBA:
p.y.SetGray(x, y, color.Gray{v.R})
if (image.Point{x, y}.In(p.cb.Rect)) {
p.cb.SetGray(x, y, color.Gray{v.G})
p.cr.SetGray(x, y, color.Gray{v.B})
}
case *color.RGBA64:
p.y.SetGray(x, y, color.Gray{uint8(v.R / 0x100)})
if (image.Point{x, y}.In(p.cb.Rect)) {
p.cb.SetGray(x, y, color.Gray{uint8(v.G / 0x100)})
p.cr.SetGray(x, y, color.Gray{uint8(v.B / 0x100)})
}
}
}

View File

@@ -145,13 +145,17 @@ 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)
cmps.add(p.SampleRate, o.SampleRate)
cmps.add(p.Latency, o.Latency)
cmps.add(p.ChannelCount, o.ChannelCount)
cmps.add(p.IsBigEndian, o.IsBigEndian)
cmps.add(p.IsFloat, o.IsFloat)
cmps.add(p.IsInterleaved, o.IsInterleaved)
return cmps.fitnessDistance()
}

View File

@@ -1,15 +1,20 @@
package mediadevices
import "github.com/pion/rtp"
import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/rtp"
)
type RTPReadCloser interface {
Read() (pkts []*rtp.Packet, release func(), err error)
Close() error
codec.Controllable
}
type rtpReadCloserImpl struct {
readFn func() ([]*rtp.Packet, func(), error)
closeFn func() error
readFn func() ([]*rtp.Packet, func(), error)
closeFn func() error
controllerFn func() codec.EncoderController
}
func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
@@ -19,3 +24,7 @@ func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
func (r *rtpReadCloserImpl) Close() error {
return r.closeFn()
}
func (r *rtpReadCloserImpl) Controller() codec.EncoderController {
return r.controllerFn()
}

View File

@@ -3,6 +3,7 @@ package mediadevices
import (
"errors"
"fmt"
"github.com/pion/rtcp"
"image"
"io"
"strings"
@@ -56,6 +57,8 @@ type Track interface {
Kind() webrtc.RTPCodecType
// StreamID is the group this track belongs too. This must be unique
StreamID() string
// RID is the RTP Stearm ID for this track. This is only used for Simulcast
RID() string
// Bind binds the current track source to the given peer connection. In Pion/webrtc v3, the bind
// call will happen automatically after the SDP negotiation. Users won't need to call this manually.
Bind(webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error)
@@ -64,6 +67,8 @@ type Track interface {
Unbind(webrtc.TrackLocalContext) error
// NewRTPReader creates a new reader from the source. The reader will encode the source, and packetize
// the encoded data in RTP format with given mtu size.
//
// Note: `mtu int` will be changed to `mtu uint16` in a future update.
NewRTPReader(codecName string, ssrc uint32, mtu int) (RTPReadCloser, error)
// NewEncodedReader creates a EncodedReadCloser that reads the encoded data in codecName format
NewEncodedReader(codecName string) (EncodedReadCloser, error)
@@ -113,6 +118,11 @@ func (track *baseTrack) StreamID() string {
return generator.String()
}
// RID is only relevant if you wish to use Simulcast
func (track *baseTrack) RID() string {
return ""
}
// 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)) {
@@ -148,6 +158,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
defer track.mu.Unlock()
signalCh := make(chan chan<- struct{})
var stopRead chan struct{}
track.activePeerConnections[ctx.ID()] = signalCh
var encodedReader RTPReadCloser
@@ -173,6 +184,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
var doneCh chan<- struct{}
writer := ctx.WriteStream()
defer func() {
close(stopRead)
encodedReader.Close()
// When there's another call to unbind, it won't block since we mark the signalCh to be closed
@@ -205,6 +217,37 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
}
}()
keyFrameController, ok := encodedReader.Controller().(codec.KeyFrameController)
if ok {
stopRead = make(chan struct{})
go func() {
reader := ctx.ReadStream()
for {
select {
case <-stopRead:
return
default:
}
pkts, _, err := reader.ReadRTCP()
if err != nil {
track.onError(err)
return
}
for _, pkt := range pkts {
switch pkt.(type) {
case *rtcp.PictureLossIndication, *rtcp.FullIntraRequest:
if err := keyFrameController.ForceKeyFrame(); err != nil {
track.onError(err)
return
}
}
}
}
}()
}
return selectedCodec, nil
}
@@ -252,6 +295,7 @@ func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, sele
type VideoTrack struct {
*baseTrack
*video.Broadcaster
ShouldCopyFrames bool
}
// NewVideoTrack constructs a new VideoTrack
@@ -303,7 +347,7 @@ func (track *VideoTrack) Unbind(ctx webrtc.TrackLocalContext) error {
}
func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadCloser, *codec.RTPCodec, error) {
reader := track.NewReader(false)
reader := track.NewReader(track.ShouldCopyFrames)
inputProp, err := detectCurrentVideoProp(track.Broadcaster)
if err != nil {
return nil, nil, err
@@ -325,7 +369,8 @@ func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadClos
}
return buffer, release, err
},
closeFn: encodedReader.Close,
closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, selectedCodec, nil
}
@@ -348,7 +393,7 @@ func (track *VideoTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
return nil, err
}
packetizer := rtp.NewPacketizer(mtu, uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
packetizer := rtp.NewPacketizer(uint16(mtu), uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
return &rtpReadCloserImpl{
readFn: func() ([]*rtp.Packet, func(), error) {
@@ -363,7 +408,8 @@ func (track *VideoTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
pkts := packetizer.Packetize(encoded.Data, encoded.Samples)
return pkts, release, err
},
closeFn: encodedReader.Close,
closeFn: encodedReader.Close,
controllerFn: encodedReader.Controller,
}, nil
}
@@ -374,7 +420,7 @@ type AudioTrack struct {
*audio.Broadcaster
}
// NewAudioTrack constructs a new VideoTrack
// NewAudioTrack constructs a new AudioTrack
func NewAudioTrack(source AudioSource, selector *CodecSelector) Track {
return newAudioTrackFromReader(source, source, selector)
}
@@ -468,7 +514,7 @@ func (track *AudioTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
return nil, err
}
packetizer := rtp.NewPacketizer(mtu, uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
packetizer := rtp.NewPacketizer(uint16(mtu), uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
return &rtpReadCloserImpl{
readFn: func() ([]*rtp.Packet, func(), error) {