mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 12:52:20 +08:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
69f9cbe008 | ||
![]() |
b5acc5d7f6 | ||
![]() |
55e65027f9 | ||
![]() |
f0ff9261b4 | ||
![]() |
08a396571f | ||
![]() |
0d09f7f458 | ||
![]() |
e780bdc6f9 | ||
![]() |
ff18b21629 | ||
![]() |
eaf9ff42a8 | ||
![]() |
5ba49e03e7 | ||
![]() |
1250e06923 | ||
![]() |
651c847674 | ||
![]() |
3b2316081e | ||
![]() |
70261260cb | ||
![]() |
548cdac668 | ||
![]() |
79f9fc31f6 | ||
![]() |
1f92ea40da | ||
![]() |
4beb7e5a23 | ||
![]() |
9bb5755cd2 | ||
![]() |
e316b30964 | ||
![]() |
596b8c4e11 | ||
![]() |
be5f684ea6 | ||
![]() |
a88c2daf89 | ||
![]() |
1f313a9d61 | ||
![]() |
19eaf375ff | ||
![]() |
b3c94a1f7b | ||
![]() |
86cb9f8ce8 | ||
![]() |
f8d1f974cf | ||
![]() |
809f74cafc | ||
![]() |
eb2db82766 | ||
![]() |
b4c6eb5409 | ||
![]() |
b263026d52 | ||
![]() |
070ab924f9 | ||
![]() |
23177a5d75 | ||
![]() |
3a04686875 | ||
![]() |
5f95b84719 | ||
![]() |
8919ba4fe5 | ||
![]() |
153c36e461 | ||
![]() |
10769b702e | ||
![]() |
2948735964 | ||
![]() |
ba848b3416 | ||
![]() |
94c6b66e46 | ||
![]() |
96fd92142c | ||
![]() |
d71b72c64d | ||
![]() |
8c2c8a9b27 | ||
![]() |
1e03f61b4b | ||
![]() |
b863c105c8 | ||
![]() |
6411b00e93 | ||
![]() |
acd2cb992b | ||
![]() |
dafd208de7 | ||
![]() |
fcec5a9149 | ||
![]() |
3d3830f7ff | ||
![]() |
655b513810 | ||
![]() |
eaaaacfc6b | ||
![]() |
fa95e47bad | ||
![]() |
020de77bc9 | ||
![]() |
33b6412c26 | ||
![]() |
f29d08ae6b |
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [ '1.15', '1.14' ]
|
||||
go: [ '1.16', '1.15' ]
|
||||
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,20 +32,20 @@ jobs:
|
||||
libx264-dev
|
||||
- name: Run Test Suite
|
||||
run: make test
|
||||
- uses: codecov/codecov-action@v1
|
||||
if: matrix.go == '1.15'
|
||||
- uses: codecov/codecov-action@v3
|
||||
if: matrix.go == '1.16'
|
||||
build-darwin:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [ '1.15', '1.14' ]
|
||||
go: [ '1.16', '1.15' ]
|
||||
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,11 +62,11 @@ 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.15'
|
||||
go-version: '1.16'
|
||||
- name: Installing go-licenses
|
||||
run: go get github.com/google/go-licenses
|
||||
- name: Checking licenses
|
||||
|
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: fix
|
||||
|
6
Makefile
6
Makefile
@@ -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)
|
||||
|
47
dockerfiles/darwin-arm64.Dockerfile
Normal file
47
dockerfiles/darwin-arm64.Dockerfile
Normal 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}
|
8
dockerfiles/darwin-arm64.cmake
Normal file
8
dockerfiles/darwin-arm64.cmake
Normal 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})
|
1
examples/.gitignore
vendored
1
examples/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
go.sum
|
@@ -5,4 +5,4 @@ examples := $(filter-out internal,$(examples))
|
||||
all: $(examples)
|
||||
|
||||
$(examples):
|
||||
cd $@ && go build
|
||||
cd $@ && go build -mod=mod
|
||||
|
@@ -2,8 +2,10 @@ module github.com/pion/mediadevices/examples
|
||||
|
||||
go 1.14
|
||||
|
||||
// Please don't commit require entries of examples.
|
||||
// `git checkout master examples/go.mod` to revert this file.
|
||||
require github.com/pion/mediadevices v0.0.0
|
||||
require (
|
||||
github.com/esimov/pigo v1.4.3
|
||||
github.com/pion/mediadevices v0.0.0
|
||||
github.com/pion/webrtc/v3 v3.0.20
|
||||
)
|
||||
|
||||
replace github.com/pion/mediadevices v0.0.0 => ../
|
||||
|
151
examples/go.sum
Normal file
151
examples/go.sum
Normal file
@@ -0,0 +1,151 @@
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
github.com/esimov/pigo v1.4.3 h1:xl098Z9CHmouywvyRZepuKx8aSWHBs/0lZtp7Yt5g28=
|
||||
github.com/esimov/pigo v1.4.3/go.mod h1:aOTYpOWsqniACzXKdSOGkqI6CnWQpP8tFjgtUOARoEs=
|
||||
github.com/fogleman/gg v1.0.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gen2brain/malgo v0.10.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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0/go.mod h1:ZceVWGtzUZmxyN+/1I+oG31oOm1dOA2QUNbua9TLVdE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/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/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/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/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=
|
||||
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 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
|
||||
github.com/pion/rtp v1.6.2/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/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/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/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=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/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/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-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-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.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
46
examples/vnc/README.md
Normal 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
127
examples/vnc/main.go
Normal 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 {}
|
||||
}
|
18
go.mod
18
go.mod
@@ -3,17 +3,13 @@ module github.com/pion/mediadevices
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
|
||||
github.com/gen2brain/malgo v0.10.29
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 // indirect
|
||||
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.2
|
||||
github.com/pion/webrtc/v3 v3.0.5
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6
|
||||
github.com/pion/rtp v1.7.13
|
||||
github.com/pion/webrtc/v3 v3.1.34
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
150
go.sum
150
go.sum
@@ -1,16 +1,15 @@
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd h1:u7K2oMFMd8APDV3fM1j2rO3U/XJf1g1qC3DDTKou8iM=
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd/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/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=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -18,102 +17,111 @@ 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.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.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
|
||||
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=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 h1:4BxFx5XCtXc+nFtXDGDW+Uu5sPtsAbvPh6RObj3fG9o=
|
||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/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.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.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
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.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
|
||||
github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
|
||||
github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ=
|
||||
github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI=
|
||||
github.com/pion/interceptor v0.0.9 h1:fk5hTdyLO3KURQsf/+RjMpEm4NE3yeTY9Kh97b5BvwA=
|
||||
github.com/pion/interceptor v0.0.9/go.mod h1:dHgEP5dtxOTf21MObuBAjJeAayPxLUAZjerGH8Xr07c=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||
github.com/pion/interceptor v0.1.10 h1:DJ2GjMGm4XGIQgMJxuEpdaExdY/6RdngT7Uh4oVmquU=
|
||||
github.com/pion/interceptor v0.1.10/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
||||
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
|
||||
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 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
|
||||
github.com/pion/rtp v1.6.2/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.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
|
||||
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
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.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
|
||||
github.com/pion/srtp/v2 v2.0.1/go.mod h1:c8NWHhhkFf/drmHTAblkdu8++lsISEBBdAuiyxgqIsE=
|
||||
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.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
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.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
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.5 h1:utennp7RwX+2mtyMzoOXE03IUIckiHBigjarRJZ2DqY=
|
||||
github.com/pion/webrtc/v3 v3.0.5/go.mod h1:/EDCREM8y+JrJSkoCRHpoz//qtuBCOYV4E96vEK3bz0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.34 h1:GUfv2zxWge77x1FhZ6Fge8KQd3bTzvX1CMN1/LmTazM=
|
||||
github.com/pion/webrtc/v3 v3.1.34/go.mod h1:jClfnbJzt8wtmewGxhPzgE5wZ0U/gNB78XCtmc/uz3k=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
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-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd h1:zYlwaUHTmxuf6H7hwO2dgwqozQmH7zf4x+/qql4oVWc=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -121,16 +129,25 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-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.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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -140,6 +157,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=
|
||||
@@ -149,5 +168,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=
|
||||
|
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,10 @@ func (track *mockMediaStreamTrack) StreamID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (track *mockMediaStreamTrack) RID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (track *mockMediaStreamTrack) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
@@ -133,9 +133,6 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
||||
case AVBindFrameFormatI420:
|
||||
*pFourCC = kCVPixelFormatType_420YpCbCr8Planar;
|
||||
break;
|
||||
case AVBindFrameFormatNV21:
|
||||
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
|
||||
break;
|
||||
case AVBindFrameFormatNV12:
|
||||
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
break;
|
||||
@@ -159,8 +156,6 @@ STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
||||
*pFormat = AVBindFrameFormatI420;
|
||||
break;
|
||||
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
||||
*pFormat = AVBindFrameFormatNV21;
|
||||
break;
|
||||
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
||||
*pFormat = AVBindFrameFormatNV12;
|
||||
break;
|
||||
@@ -171,8 +166,8 @@ STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
||||
*pFormat = AVBindFrameFormatYUY2;
|
||||
break;
|
||||
// TODO: Add the rest of frame formats
|
||||
default:
|
||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||
default:
|
||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||
}
|
||||
return retStatus;
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
@@ -13,6 +15,9 @@ import (
|
||||
type RTPCodec struct {
|
||||
webrtc.RTPCodecParameters
|
||||
rtp.Payloader
|
||||
|
||||
// Latency of static frame size codec.
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
// NewRTPH264Codec is a helper to create an H264 codec
|
||||
|
121
pkg/codec/internal/codectest/codectest.go
Normal file
121
pkg/codec/internal/codectest/codectest.go
Normal 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)
|
||||
}
|
||||
}
|
46
pkg/codec/mmal/mmal_test.go
Normal file
46
pkg/codec/mmal/mmal_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package mmal
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"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,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
@@ -80,12 +80,16 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
|
||||
SFrameBSInfo info = {0};
|
||||
Slice payload = {0};
|
||||
|
||||
if(e->force_key_frame == 1) {
|
||||
e->engine->ForceIntraFrame(true);
|
||||
e->force_key_frame = 0;
|
||||
}
|
||||
|
||||
pic.iPicWidth = f.width;
|
||||
pic.iPicHeight = f.height;
|
||||
pic.iColorFormat = videoFormatI420;
|
||||
// We always received I420 format
|
||||
pic.iStride[0] = pic.iPicWidth;
|
||||
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
|
||||
pic.iStride[0] = f.ystride;
|
||||
pic.iStride[1] = pic.iStride[2] = f.cstride;
|
||||
pic.pData[0] = (unsigned char *)f.y;
|
||||
pic.pData[1] = (unsigned char *)f.u;
|
||||
pic.pData[2] = (unsigned char *)f.v;
|
||||
|
@@ -12,6 +12,8 @@ typedef struct Slice {
|
||||
|
||||
typedef struct Frame {
|
||||
void *y, *u, *v;
|
||||
int ystride;
|
||||
int cstride;
|
||||
int height;
|
||||
int width;
|
||||
} Frame;
|
||||
@@ -27,6 +29,7 @@ typedef struct Encoder {
|
||||
ISVCEncoder *engine;
|
||||
unsigned char *buff;
|
||||
int buff_size;
|
||||
int force_key_frame;
|
||||
} Encoder;
|
||||
|
||||
Encoder *enc_new(const EncoderOptions params, int *eresult);
|
||||
|
BIN
pkg/codec/openh264/lib/libopenh264-darwin-arm64.a
Normal file
BIN
pkg/codec/openh264/lib/libopenh264-darwin-arm64.a
Normal file
Binary file not shown.
@@ -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)
|
||||
@@ -84,13 +86,18 @@ func (e *encoder) SetBitRate(b int) error {
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
e.engine.force_key_frame = C.int(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.closed = true
|
||||
|
||||
var rv C.int
|
||||
|
46
pkg/codec/openh264/openh264_test.go
Normal file
46
pkg/codec/openh264/openh264_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package openh264
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"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,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
BIN
pkg/codec/opus/lib/libopus-darwin-arm64.a
Normal file
BIN
pkg/codec/opus/lib/libopus-darwin-arm64.a
Normal file
Binary file not shown.
@@ -3,7 +3,6 @@ package opus
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
@@ -28,8 +27,6 @@ type encoder struct {
|
||||
engine *C.OpusEncoder
|
||||
}
|
||||
|
||||
var latencies = []float64{5, 10, 20, 40, 60}
|
||||
|
||||
func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
|
||||
var cerror C.int
|
||||
|
||||
@@ -37,10 +34,6 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
||||
return nil, fmt.Errorf("opus: inProp.SampleRate is required")
|
||||
}
|
||||
|
||||
if p.Latency == 0 {
|
||||
p.Latency = 20
|
||||
}
|
||||
|
||||
if params.BitRate == 0 {
|
||||
params.BitRate = 32000
|
||||
}
|
||||
@@ -49,19 +42,8 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
||||
params.ChannelMixer = &mixer.MonoMixer{}
|
||||
}
|
||||
|
||||
// Select the nearest supported latency
|
||||
var targetLatency float64
|
||||
// TODO: use p.Latency.Milliseconds() after Go 1.12 EOL
|
||||
latencyInMS := float64(p.Latency.Nanoseconds() / 1000000)
|
||||
nearestDist := math.Inf(+1)
|
||||
for _, latency := range latencies {
|
||||
dist := math.Abs(latency - latencyInMS)
|
||||
if dist >= nearestDist {
|
||||
break
|
||||
}
|
||||
|
||||
nearestDist = dist
|
||||
targetLatency = latency
|
||||
if !params.Latency.Validate() {
|
||||
return nil, fmt.Errorf("opus: unsupported latency %v", params.Latency)
|
||||
}
|
||||
|
||||
channels := p.ChannelCount
|
||||
@@ -77,7 +59,7 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
||||
}
|
||||
|
||||
rMix := audio.NewChannelMixer(channels, params.ChannelMixer)
|
||||
rBuf := audio.NewBuffer(int(targetLatency * float64(p.SampleRate) / 1000))
|
||||
rBuf := audio.NewBuffer(params.Latency.samples(p.SampleRate))
|
||||
e := encoder{
|
||||
engine: engine,
|
||||
reader: rMix(rBuf(r)),
|
||||
@@ -144,6 +126,9 @@ func (e *encoder) ForceKeyFrame() error {
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
if e.engine == nil {
|
||||
return nil
|
||||
}
|
||||
C.opus_encoder_destroy(e.engine)
|
||||
e.engine = nil
|
||||
return nil
|
||||
|
@@ -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"
|
||||
|
43
pkg/codec/opus/opus_test.go
Normal file
43
pkg/codec/opus/opus_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package opus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
"github.com/pion/mediadevices/pkg/wave"
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
@@ -1,27 +1,69 @@
|
||||
package opus
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
"github.com/pion/mediadevices/pkg/wave/mixer"
|
||||
)
|
||||
|
||||
// Latency is a type of OPUS codec frame duration.
|
||||
type Latency time.Duration
|
||||
|
||||
// Latency values available in OPUS codec.
|
||||
const (
|
||||
Latency2500us Latency = Latency(2500 * time.Microsecond)
|
||||
Latency5ms Latency = Latency(5 * time.Millisecond)
|
||||
Latency10ms Latency = Latency(10 * time.Millisecond)
|
||||
Latency20ms Latency = Latency(20 * time.Millisecond)
|
||||
Latency40ms Latency = Latency(40 * time.Millisecond)
|
||||
Latency60ms Latency = Latency(60 * time.Millisecond)
|
||||
)
|
||||
|
||||
// Validate that the Latency is allowed in OPUS.
|
||||
func (l Latency) Validate() bool {
|
||||
switch l {
|
||||
case Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Duration returns latency in time.Duration.
|
||||
func (l Latency) Duration() time.Duration {
|
||||
return time.Duration(l)
|
||||
}
|
||||
|
||||
// samples returns number of samples for given sample rate.
|
||||
func (l Latency) samples(sampleRate int) int {
|
||||
return int(l.Duration() * time.Duration(sampleRate) / time.Second)
|
||||
}
|
||||
|
||||
// Params stores opus specific encoding parameters.
|
||||
type Params struct {
|
||||
codec.BaseParams
|
||||
// ChannelMixer is a mixer to be used if number of given and expected channels differ.
|
||||
ChannelMixer mixer.ChannelMixer
|
||||
|
||||
// Expected latency of the codec.
|
||||
Latency Latency
|
||||
}
|
||||
|
||||
// NewParams returns default opus codec specific parameters.
|
||||
func NewParams() (Params, error) {
|
||||
return Params{}, nil
|
||||
return Params{
|
||||
Latency: Latency20ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RTPCodec represents the codec metadata
|
||||
func (p *Params) RTPCodec() *codec.RTPCodec {
|
||||
return codec.NewRTPOpusCodec(48000)
|
||||
c := codec.NewRTPOpusCodec(48000)
|
||||
c.Latency = time.Duration(p.Latency)
|
||||
return c
|
||||
}
|
||||
|
||||
// BuildAudioEncoder builds opus encoder with given params
|
||||
|
49
pkg/codec/opus/params_test.go
Normal file
49
pkg/codec/opus/params_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLatency_Validate(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
for _, l := range []Latency{
|
||||
Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms,
|
||||
} {
|
||||
if !l.Validate() {
|
||||
t.Errorf("Defined Latency(%v) must be valid", l)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
for _, l := range []Latency{
|
||||
0, Latency(time.Second),
|
||||
} {
|
||||
if l.Validate() {
|
||||
t.Errorf("Latency(%v) must be valid", l)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLatency_samples(t *testing.T) {
|
||||
testCases := []struct {
|
||||
latency Latency
|
||||
sampleRate int
|
||||
samples int
|
||||
}{
|
||||
{Latency5ms, 48000, 240},
|
||||
{Latency20ms, 16000, 320},
|
||||
{Latency20ms, 48000, 960},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(fmt.Sprintf("%v_%d", time.Duration(testCase.latency), testCase.sampleRate), func(t *testing.T) {
|
||||
samples := testCase.latency.samples(testCase.sampleRate)
|
||||
if samples != testCase.samples {
|
||||
t.Errorf("Expected samples: %d, got: %d", testCase.samples, samples)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -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.
|
||||
|
70
pkg/codec/vaapi/vaapi_test.go
Normal file
70
pkg/codec/vaapi/vaapi_test.go
Normal 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,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vaapi
|
||||
@@ -552,6 +553,10 @@ 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)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vaapi
|
||||
@@ -487,6 +488,10 @@ 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)
|
||||
|
@@ -16,6 +16,7 @@ type Params struct {
|
||||
RateControlOvershootPercent uint
|
||||
RateControlMinQuantizer uint
|
||||
RateControlMaxQuantizer uint
|
||||
LagInFrames uint
|
||||
ErrorResilient ErrorResilientMode
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,9 @@ package vpx
|
||||
// int pktSz(vpx_codec_cx_pkt_t *pkt) {
|
||||
// return pkt->data.frame.sz;
|
||||
// }
|
||||
// vpx_codec_frame_flags_t pktFrameFlags(vpx_codec_cx_pkt_t *pkt) {
|
||||
// return pkt->data.frame.flags;
|
||||
// }
|
||||
//
|
||||
// // Alloc helpers
|
||||
// vpx_codec_ctx_t *newCtx() {
|
||||
@@ -61,15 +64,17 @@ import (
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
codec *C.vpx_codec_ctx_t
|
||||
raw *C.vpx_image_t
|
||||
cfg *C.vpx_codec_enc_cfg_t
|
||||
r video.Reader
|
||||
frameIndex int
|
||||
tStart int
|
||||
tLastFrame int
|
||||
frame []byte
|
||||
deadline int
|
||||
codec *C.vpx_codec_ctx_t
|
||||
raw *C.vpx_image_t
|
||||
cfg *C.vpx_codec_enc_cfg_t
|
||||
r video.Reader
|
||||
frameIndex int
|
||||
tStart int
|
||||
tLastFrame int
|
||||
frame []byte
|
||||
deadline int
|
||||
requireKeyFrame bool
|
||||
isKeyFrame bool
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
@@ -141,6 +146,7 @@ func newParams(codecIface *C.vpx_codec_iface_t) (Params, error) {
|
||||
RateControlOvershootPercent: uint(cfg.rc_overshoot_pct),
|
||||
RateControlMinQuantizer: uint(cfg.rc_min_quantizer),
|
||||
RateControlMaxQuantizer: uint(cfg.rc_max_quantizer),
|
||||
LagInFrames: uint(cfg.g_lag_in_frames),
|
||||
ErrorResilient: ErrorResilientMode(cfg.g_error_resilient),
|
||||
}, nil
|
||||
}
|
||||
@@ -171,6 +177,7 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
|
||||
cfg.g_h = C.uint(p.Height)
|
||||
cfg.g_timebase.num = 1
|
||||
cfg.g_timebase.den = 1000
|
||||
cfg.g_lag_in_frames = C.uint(params.LagInFrames)
|
||||
cfg.rc_target_bitrate = C.uint(params.BitRate) / 1000
|
||||
cfg.kf_max_dist = C.uint(params.KeyFrameInterval)
|
||||
|
||||
@@ -254,6 +261,9 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
duration = 1
|
||||
}
|
||||
var flags int
|
||||
if e.requireKeyFrame {
|
||||
flags = flags | C.VPX_EFLAG_FORCE_KF
|
||||
}
|
||||
if ec := C.encode_wrapper(
|
||||
e.codec, e.raw,
|
||||
C.long(t-e.tStart), C.ulong(duration), C.long(flags), C.ulong(e.deadline),
|
||||
@@ -262,6 +272,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return nil, func() {}, fmt.Errorf("vpx_codec_encode failed (%d)", ec)
|
||||
}
|
||||
|
||||
e.requireKeyFrame = false
|
||||
e.frameIndex++
|
||||
e.tLastFrame = t
|
||||
|
||||
@@ -273,6 +284,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
break
|
||||
}
|
||||
if pkt.kind == C.VPX_CODEC_CX_FRAME_PKT {
|
||||
e.isKeyFrame = C.pktFrameFlags(pkt)&C.VPX_FRAME_IS_KEY == C.VPX_FRAME_IS_KEY
|
||||
encoded := C.GoBytes(unsafe.Pointer(C.pktBuf(pkt)), C.pktSz(pkt))
|
||||
e.frame = append(e.frame, encoded...)
|
||||
}
|
||||
@@ -288,13 +300,20 @@ func (e *encoder) SetBitRate(b int) error {
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.requireKeyFrame = true
|
||||
return nil
|
||||
}
|
||||
|
||||
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))
|
||||
|
@@ -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) {
|
||||
@@ -20,6 +72,8 @@ func TestImageSizeChange(t *testing.T) {
|
||||
},
|
||||
"VP9": func() (codec.VideoEncoderBuilder, error) {
|
||||
p, err := NewVP9Params()
|
||||
// Disable latency to ease test and begin to receive packets for each input frame
|
||||
p.LagInFrames = 0
|
||||
return &p, err
|
||||
},
|
||||
} {
|
||||
@@ -87,3 +141,72 @@ func TestImageSizeChange(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestKeyFrame(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()
|
||||
// Disable latency to ease test and begin to receive packets for each input frame
|
||||
p.LagInFrames = 0
|
||||
return &p, err
|
||||
},
|
||||
} {
|
||||
factory := factory
|
||||
t.Run(name, func(t *testing.T) {
|
||||
param, err := factory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var initialWidth, initialHeight, width, height int = 320, 240, 320, 240
|
||||
|
||||
var cnt uint32
|
||||
r, err := param.BuildVideoEncoder(
|
||||
video.ReaderFunc(func() (image.Image, func(), error) {
|
||||
i := atomic.AddUint32(&cnt, 1)
|
||||
if i == 3 {
|
||||
return nil, nil, io.EOF
|
||||
}
|
||||
return image.NewYCbCr(
|
||||
image.Rect(0, 0, width, height),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
), func() {}, nil
|
||||
}),
|
||||
prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: initialWidth,
|
||||
Height: initialHeight,
|
||||
FrameRate: 1,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, rel, err := r.Read()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rel()
|
||||
r.ForceKeyFrame()
|
||||
_, rel, err = r.Read()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !r.(*encoder).isKeyFrame {
|
||||
t.Fatal("Not a key frame")
|
||||
}
|
||||
rel()
|
||||
_, _, err = r.Read()
|
||||
if err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -129,7 +129,8 @@ func (e *encoder) SetBitRate(b int) error {
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
e.engine.force_key_frame = C.int(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
|
48
pkg/codec/x264/x264_test.go
Normal file
48
pkg/codec/x264/x264_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package x264
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"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,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
@@ -1 +1,16 @@
|
||||
/*
|
||||
Package camera provides a video camera driver.
|
||||
|
||||
Device Label Generation Rules
|
||||
|
||||
On Linux, the device label will be in the format of:
|
||||
pci-0000:00:00.0-usb-0:0:0.0-video-index0;video0
|
||||
If /dev/v4l/by-path/* is not available (for example in a docker container without
|
||||
bindings in /dev/v4l/by-path/), it will be:
|
||||
video0;video0
|
||||
*/
|
||||
package camera
|
||||
|
||||
// LabelSeparator is used to separate labels for a driver that
|
||||
// is found from multiple locations on a host.
|
||||
const LabelSeparator = ";"
|
||||
|
@@ -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 {
|
||||
|
@@ -73,51 +73,51 @@ type camera struct {
|
||||
|
||||
func init() {
|
||||
discovered := make(map[string]struct{})
|
||||
discover(discovered, "/dev/v4l/by-path/*")
|
||||
discover(discovered, "/dev/video*")
|
||||
}
|
||||
|
||||
discover := func(pattern string) {
|
||||
devices, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
// No v4l device.
|
||||
return
|
||||
}
|
||||
for _, device := range devices {
|
||||
label := filepath.Base(device)
|
||||
reallink, err := os.Readlink(device)
|
||||
if err != nil {
|
||||
reallink = label
|
||||
} else {
|
||||
reallink = filepath.Base(reallink)
|
||||
}
|
||||
|
||||
if _, ok := discovered[reallink]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
discovered[reallink] = struct{}{}
|
||||
cam := newCamera(device)
|
||||
priority := driver.PriorityNormal
|
||||
if label == prioritizedDevice {
|
||||
priority = driver.PriorityHigh
|
||||
}
|
||||
driver.GetManager().Register(cam, driver.Info{
|
||||
Label: label,
|
||||
DeviceType: driver.Camera,
|
||||
Priority: priority,
|
||||
})
|
||||
}
|
||||
func discover(discovered map[string]struct{}, pattern string) {
|
||||
devices, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
// No v4l device.
|
||||
return
|
||||
}
|
||||
for _, device := range devices {
|
||||
label := filepath.Base(device)
|
||||
reallink, err := os.Readlink(device)
|
||||
if err != nil {
|
||||
reallink = label
|
||||
} else {
|
||||
reallink = filepath.Base(reallink)
|
||||
}
|
||||
if _, ok := discovered[reallink]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
discover("/dev/v4l/by-path/*")
|
||||
discover("/dev/video*")
|
||||
discovered[reallink] = struct{}{}
|
||||
cam := newCamera(device)
|
||||
priority := driver.PriorityNormal
|
||||
if reallink == prioritizedDevice {
|
||||
priority = driver.PriorityHigh
|
||||
}
|
||||
driver.GetManager().Register(cam, driver.Info{
|
||||
Label: label + LabelSeparator + reallink,
|
||||
DeviceType: driver.Camera,
|
||||
Priority: priority,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newCamera(path string) *camera {
|
||||
formats := map[webcam.PixelFormat]frame.Format{
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_YUV420): frame.FormatI420,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_NV21): frame.FormatNV21,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV12,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_YUYV): frame.FormatYUYV,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_UYVY): frame.FormatUYVY,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV21,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_MJPEG): frame.FormatMJPEG,
|
||||
webcam.PixelFormat(C.V4L2_PIX_FMT_Z16): frame.FormatZ16,
|
||||
}
|
||||
|
||||
reversedFormats := make(map[frame.Format]webcam.PixelFormat)
|
||||
@@ -179,6 +179,13 @@ 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
|
||||
}
|
||||
|
72
pkg/driver/camera/camera_linux_test.go
Normal file
72
pkg/driver/camera/camera_linux_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/driver"
|
||||
)
|
||||
|
||||
func TestDiscover(t *testing.T) {
|
||||
const (
|
||||
shortName = "unittest-video0"
|
||||
shortName2 = "unittest-video1"
|
||||
longName = "unittest-long-device-name:0:1:2:3"
|
||||
)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
byPathDir := filepath.Join(dir, "v4l", "by-path")
|
||||
if err := os.MkdirAll(byPathDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(
|
||||
filepath.Join(dir, shortName),
|
||||
filepath.Join(byPathDir, longName),
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
discovered := make(map[string]struct{})
|
||||
discover(discovered, filepath.Join(byPathDir, "*"))
|
||||
discover(discovered, filepath.Join(dir, "unittest-video*"))
|
||||
|
||||
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
|
||||
// Ignore real cameras.
|
||||
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "unittest")
|
||||
})
|
||||
if len(drvs) != 2 {
|
||||
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
|
||||
}
|
||||
|
||||
labels := []string{
|
||||
drvs[0].Info().Label,
|
||||
drvs[1].Info().Label,
|
||||
}
|
||||
// Returned drivers are unordered. Sort to get static result.
|
||||
sort.Sort(sort.StringSlice(labels))
|
||||
|
||||
expected := longName + LabelSeparator + shortName
|
||||
if label := labels[0]; label != expected {
|
||||
t.Errorf("Expected label: %s, got: %s", expected, label)
|
||||
}
|
||||
|
||||
expectedNoLink := shortName2 + LabelSeparator + shortName2
|
||||
if label := labels[1]; label != expectedNoLink {
|
||||
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
@@ -155,9 +157,6 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
|
||||
return decodedChunk, func() {}, err
|
||||
})
|
||||
|
||||
// FIXME: The current audio detection and audio encoder can only work with a static latency. Since the latency from the driver
|
||||
// can fluctuate, we need to stabilize it. Maybe there's a better way for doing this?
|
||||
reader = audio.NewBuffer(int(inputProp.Latency.Seconds() * float64(inputProp.SampleRate)))(reader)
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package screen
|
||||
|
||||
// #cgo pkg-config: x11 xext
|
||||
// #include <stdint.h>
|
||||
// #include <string.h>
|
||||
// #include <sys/shm.h>
|
||||
// #include <X11/Xlib.h>
|
||||
// #define XUTIL_DEFINE_FUNCTIONS
|
||||
@@ -125,6 +126,14 @@ func (c colorFunc) RGBA() (r, g, b, a uint32) {
|
||||
|
||||
func (s *shmImage) At(x, y int) color.Color {
|
||||
switch s.pixFmt {
|
||||
case pixFmtRGB24:
|
||||
addr := (x + y*int(s.img.width)) * 4
|
||||
r := uint32(s.b[addr]) * 0x100
|
||||
g := uint32(s.b[addr+1]) * 0x100
|
||||
b := uint32(s.b[addr+2]) * 0x100
|
||||
return colorFunc(func() (_, _, _, _ uint32) {
|
||||
return r, g, b, 0xFFFF
|
||||
})
|
||||
case pixFmtBGR24:
|
||||
addr := (x + y*int(s.img.width)) * 4
|
||||
b := uint32(s.b[addr]) * 0x100
|
||||
@@ -133,6 +142,15 @@ func (s *shmImage) At(x, y int) color.Color {
|
||||
return colorFunc(func() (_, _, _, _ uint32) {
|
||||
return r, g, b, 0xFFFF
|
||||
})
|
||||
case pixFmtRGB16:
|
||||
addr := (x + y*int(s.img.width)) * 2
|
||||
b1, b2 := s.b[addr], s.b[addr+1]
|
||||
r := uint32(b1>>3) * 0x100
|
||||
g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100
|
||||
b := uint32(b2&0x1F) * 0x100
|
||||
return colorFunc(func() (_, _, _, _ uint32) {
|
||||
return r, g, b, 0xFFFF
|
||||
})
|
||||
case pixFmtBGR16:
|
||||
addr := (x + y*int(s.img.width)) * 2
|
||||
b1, b2 := s.b[addr], s.b[addr+1]
|
||||
@@ -149,12 +167,25 @@ func (s *shmImage) At(x, y int) color.Color {
|
||||
|
||||
func (s *shmImage) RGBAAt(x, y int) color.RGBA {
|
||||
switch s.pixFmt {
|
||||
case pixFmtRGB24:
|
||||
addr := (x + y*int(s.img.width)) * 4
|
||||
r := s.b[addr]
|
||||
g := s.b[addr+1]
|
||||
b := s.b[addr+2]
|
||||
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||
case pixFmtBGR24:
|
||||
addr := (x + y*int(s.img.width)) * 4
|
||||
b := s.b[addr]
|
||||
g := s.b[addr+1]
|
||||
r := s.b[addr+2]
|
||||
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||
case pixFmtRGB16:
|
||||
addr := (x + y*int(s.img.width)) * 2
|
||||
b1, b2 := s.b[addr], s.b[addr+1]
|
||||
r := b1 >> 3
|
||||
g := (b1&0x7)<<3 | (b2&0xE0)>>5
|
||||
b := b2 & 0x1F
|
||||
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||
case pixFmtBGR16:
|
||||
addr := (x + y*int(s.img.width)) * 2
|
||||
b1, b2 := s.b[addr], s.b[addr+1]
|
||||
@@ -178,11 +209,17 @@ func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA {
|
||||
dst.Pix = dst.Pix[:l]
|
||||
}
|
||||
switch s.pixFmt {
|
||||
case pixFmtRGB24:
|
||||
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
|
||||
return dst
|
||||
case pixFmtBGR24:
|
||||
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
|
||||
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
|
||||
return dst
|
||||
case pixFmtRGB16:
|
||||
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
|
||||
return dst
|
||||
case pixFmtBGR16:
|
||||
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
|
||||
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
|
||||
return dst
|
||||
default:
|
||||
panic("unsupported pixel format")
|
||||
@@ -199,8 +236,12 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
|
||||
s := &shmImage{dp: dp}
|
||||
|
||||
switch {
|
||||
case v.red_mask == 0xFF && v.green_mask == 0xFF00 && v.blue_mask == 0xFF0000:
|
||||
s.pixFmt = pixFmtRGB24
|
||||
case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF:
|
||||
s.pixFmt = pixFmtBGR24
|
||||
case v.red_mask == 0x1F && v.green_mask == 0x7E0 && v.blue_mask == 0xF800:
|
||||
s.pixFmt = pixFmtRGB16
|
||||
case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F:
|
||||
s.pixFmt = pixFmtBGR16
|
||||
default:
|
||||
@@ -209,7 +250,7 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
|
||||
return nil, errors.New("unsupported pixel format")
|
||||
}
|
||||
|
||||
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.ulong(w*h*4+8), C.IPC_CREAT|0600)
|
||||
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.size_t(w*h*4+8), C.IPC_CREAT|0600)
|
||||
if s.shm.shmid == -1 {
|
||||
return nil, errors.New("failed to get shared memory")
|
||||
}
|
||||
@@ -279,5 +320,5 @@ func (r *reader) Close() {
|
||||
|
||||
// cAlign64 is fot testing
|
||||
func cAlign64(ptr uintptr) uintptr {
|
||||
return uintptr(C.align64ForTest(C.ulong(uintptr(ptr))))
|
||||
return uintptr(C.align64ForTest(C.size_t(uintptr(ptr))))
|
||||
}
|
||||
|
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
21
pkg/driver/vncdriver/vnc/LICENSE
Normal file
21
pkg/driver/vncdriver/vnc/LICENSE
Normal 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.
|
16
pkg/driver/vncdriver/vnc/README.md
Normal file
16
pkg/driver/vncdriver/vnc/README.md
Normal 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
|
494
pkg/driver/vncdriver/vnc/client.go
Normal file
494
pkg/driver/vncdriver/vnc/client.go
Normal 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)
|
||||
}
|
124
pkg/driver/vncdriver/vnc/client_auth.go
Normal file
124
pkg/driver/vncdriver/vnc/client_auth.go
Normal 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
|
||||
}
|
6
pkg/driver/vncdriver/vnc/color.go
Normal file
6
pkg/driver/vncdriver/vnc/color.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package vnc
|
||||
|
||||
// Color represents a single color in a color map.
|
||||
type Color struct {
|
||||
R, G, B uint16
|
||||
}
|
217
pkg/driver/vncdriver/vnc/encoding.go
Normal file
217
pkg/driver/vncdriver/vnc/encoding.go
Normal 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
|
||||
}
|
||||
}
|
151
pkg/driver/vncdriver/vnc/pixel_format.go
Normal file
151
pkg/driver/vncdriver/vnc/pixel_format.go
Normal 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
|
||||
}
|
16
pkg/driver/vncdriver/vnc/pointer.go
Normal file
16
pkg/driver/vncdriver/vnc/pointer.go
Normal 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
|
||||
)
|
192
pkg/driver/vncdriver/vnc/server_messages.go
Normal file
192
pkg/driver/vncdriver/vnc/server_messages.go
Normal 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
|
||||
}
|
180
pkg/driver/vncdriver/vncdriver.go
Normal file
180
pkg/driver/vncdriver/vncdriver.go
Normal 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@@ -1,14 +1,19 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
func wrapAdapter(a Adapter, info Info) Driver {
|
||||
id := uuid.NewV4().String()
|
||||
generator, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id := generator.String()
|
||||
d := &adapterWrapper{
|
||||
Adapter: a,
|
||||
id: id,
|
||||
|
@@ -25,6 +25,9 @@ const (
|
||||
|
||||
// FormatMJPEG https://www.fourcc.org/mjpg/
|
||||
FormatMJPEG = "MJPEG"
|
||||
|
||||
// FormatZ16 https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
|
||||
FormatZ16 = "Z16"
|
||||
)
|
||||
|
||||
const FormatYUYV = FormatYUY2
|
||||
@@ -36,6 +39,7 @@ var decoderMap = map[Format]decoderFunc{
|
||||
FormatYUY2: decodeYUY2,
|
||||
FormatUYVY: decodeUYVY,
|
||||
FormatMJPEG: decodeMJPEG,
|
||||
FormatZ16: decodeZ16,
|
||||
}
|
||||
|
||||
func NewDecoder(f Format) (Decoder, error) {
|
||||
|
36
pkg/frame/z16.go
Normal file
36
pkg/frame/z16.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package frame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
func decodeZ16(frame []byte, width, height int) (image.Image, func(), error) {
|
||||
expectedSize := 2 * (width * height)
|
||||
if expectedSize != len(frame) {
|
||||
return nil, func() {}, fmt.Errorf("frame length (%d) not expected size (%d)", len(frame), expectedSize)
|
||||
}
|
||||
img := image.NewGray16(image.Rect(0, 0, width, height))
|
||||
/*
|
||||
v4l specifies images in terms of series of lines which is perplexing because the
|
||||
example in https://www.kernel.org/doc/html/v5.9/userspace-api/media/v4l/pixfmt-z16.html
|
||||
seems to swap the subscript of each depth value.
|
||||
|
||||
Clear example:
|
||||
Width: 3, Height: 4
|
||||
[Z_low(x_0,y_0), Z_high(x_0,y_0), Z_low(x_1,y_0), Z_high(x_1,y_0), Z_low(x_2,y_0), Z_high(x_2,y_0),
|
||||
Z_low(x_0,y_1), Z_high(x_0,y_1), Z_low(x_1,y_1), Z_high(x_1,y_1), Z_low(x_2,y_1), Z_high(x_2,y_1),
|
||||
Z_low(x_0,y_2), Z_high(x_0,y_2), Z_low(x_1,y_2), Z_high(x_1,y_2), Z_low(x_2,y_2), Z_high(x_2,y_2),
|
||||
Z_low(x_0,y_3), Z_high(x_0,y_3), Z_low(x_1,y_3), Z_high(x_1,y_3), Z_low(x_2,y_3), Z_high(x_2,y_3)]
|
||||
*/
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
idx := 2 * (x + (y * width))
|
||||
z := binary.LittleEndian.Uint16(frame[idx : idx+2])
|
||||
img.SetGray16(x, y, color.Gray16{Y: z})
|
||||
}
|
||||
}
|
||||
return img, func() {}, nil
|
||||
}
|
45
pkg/frame/z16_test.go
Normal file
45
pkg/frame/z16_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package frame
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeZ16(t *testing.T) {
|
||||
const (
|
||||
width = 2
|
||||
height = 3
|
||||
)
|
||||
decoder, err := NewDecoder(FormatZ16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, _, err := decoder.Decode([]byte{0x00}, width, height)
|
||||
if err == nil {
|
||||
t.Errorf("expected to get a frame length mismatch")
|
||||
}
|
||||
|
||||
input := []byte{
|
||||
0x0c, 0x00, 0x20, 0x03,
|
||||
0xa3, 0x01, 0x10, 0x00,
|
||||
0x56, 0x09, 0x5d, 0x00,
|
||||
}
|
||||
expected := image.NewGray16(image.Rect(0, 0, width, height))
|
||||
expected.Stride = width * 2
|
||||
expected.SetGray16(0, 0, color.Gray16{Y: 12})
|
||||
expected.SetGray16(1, 0, color.Gray16{Y: 800})
|
||||
expected.SetGray16(0, 1, color.Gray16{Y: 419})
|
||||
expected.SetGray16(1, 1, color.Gray16{Y: 16})
|
||||
expected.SetGray16(0, 2, color.Gray16{Y: 2390})
|
||||
expected.SetGray16(1, 2, color.Gray16{Y: 93})
|
||||
|
||||
img, _, err = decoder.Decode(input, width, height)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, img) {
|
||||
t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, img)
|
||||
}
|
||||
}
|
@@ -107,12 +107,14 @@ func TestBroadcast(t *testing.T) {
|
||||
|
||||
fps := float64(count) / duration.Seconds()
|
||||
if fps < pauseCond.expectedFPS-2 || fps > pauseCond.expectedFPS+2 {
|
||||
t.Fatal("Unexpected average FPS")
|
||||
t.Error("Unexpected average FPS")
|
||||
return
|
||||
}
|
||||
|
||||
droppedFramesPerSecond := float64(droppedFrames) / duration.Seconds()
|
||||
if droppedFramesPerSecond < pauseCond.expectedDrop-2 || droppedFramesPerSecond > pauseCond.expectedDrop+2 {
|
||||
t.Fatal("Unexpected drop count")
|
||||
t.Error("Unexpected drop count")
|
||||
return
|
||||
}
|
||||
|
||||
fpsChan <- []float64{fps, droppedFramesPerSecond, float64(lastFrameCount)}
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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++)
|
||||
{
|
||||
|
@@ -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)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
|
||||
|
@@ -144,9 +144,11 @@ func newInt16InterleavedDecoder() (Decoder, Format) {
|
||||
container := NewInt16Interleaved(chunkInfo)
|
||||
|
||||
if endian == hostEndian {
|
||||
data := container.Data
|
||||
dst := *(*[]byte)(unsafe.Pointer(&data))
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
|
||||
n := len(chunk)
|
||||
h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[0])), Len: n, Cap: n}
|
||||
dst := *(*[]byte)(unsafe.Pointer(&h))
|
||||
hdr.Len, hdr.Cap = n, n
|
||||
copy(dst, chunk)
|
||||
return container, nil
|
||||
}
|
||||
@@ -188,9 +190,11 @@ func newInt16NonInterleavedDecoder() (Decoder, Format) {
|
||||
|
||||
if endian == hostEndian {
|
||||
for ch := 0; ch < channels; ch++ {
|
||||
data := container.Data[ch]
|
||||
dst := *(*[]byte)(unsafe.Pointer(&data))
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
|
||||
hdr.Len, hdr.Cap = chunkLen, chunkLen
|
||||
offset := ch * chunkLen
|
||||
h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[ch][0])), Len: chunkLen, Cap: chunkLen}
|
||||
dst := *(*[]byte)(unsafe.Pointer(&h))
|
||||
copy(dst, chunk[offset:offset+chunkLen])
|
||||
}
|
||||
return container, nil
|
||||
@@ -228,9 +232,11 @@ func newFloat32InterleavedDecoder() (Decoder, Format) {
|
||||
container := NewFloat32Interleaved(chunkInfo)
|
||||
|
||||
if endian == hostEndian {
|
||||
data := container.Data
|
||||
dst := *(*[]byte)(unsafe.Pointer(&data))
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
|
||||
n := len(chunk)
|
||||
h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[0])), Len: n, Cap: n}
|
||||
dst := *(*[]byte)(unsafe.Pointer(&h))
|
||||
hdr.Len, hdr.Cap = n, n
|
||||
copy(dst, chunk)
|
||||
return container, nil
|
||||
}
|
||||
@@ -272,9 +278,11 @@ func newFloat32NonInterleavedDecoder() (Decoder, Format) {
|
||||
|
||||
if endian == hostEndian {
|
||||
for ch := 0; ch < channels; ch++ {
|
||||
data := container.Data[ch]
|
||||
dst := *(*[]byte)(unsafe.Pointer(&data))
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
|
||||
hdr.Len, hdr.Cap = chunkLen, chunkLen
|
||||
offset := ch * chunkLen
|
||||
h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[ch][0])), Len: chunkLen, Cap: chunkLen}
|
||||
dst := *(*[]byte)(unsafe.Pointer(&h))
|
||||
copy(dst, chunk[offset:offset+chunkLen])
|
||||
}
|
||||
return container, nil
|
||||
|
40
track.go
40
track.go
@@ -56,6 +56,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 +66,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 +117,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)) {
|
||||
@@ -209,21 +218,30 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
}
|
||||
|
||||
func (track *baseTrack) unbind(ctx webrtc.TrackLocalContext) error {
|
||||
track.mu.Lock()
|
||||
defer track.mu.Unlock()
|
||||
|
||||
ch, ok := track.activePeerConnections[ctx.ID()]
|
||||
if !ok {
|
||||
return errNotFoundPeerConnection
|
||||
ch, err := track.removeActivePeerConnection(ctx.ID())
|
||||
if err != err {
|
||||
return err
|
||||
}
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
ch <- doneCh
|
||||
<-doneCh
|
||||
delete(track.activePeerConnections, ctx.ID())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (track *baseTrack) removeActivePeerConnection(id string) (chan<- chan<- struct{}, error) {
|
||||
track.mu.Lock()
|
||||
defer track.mu.Unlock()
|
||||
|
||||
ch, ok := track.activePeerConnections[id]
|
||||
if !ok {
|
||||
return nil, errNotFoundPeerConnection
|
||||
}
|
||||
delete(track.activePeerConnections, id)
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, selector *CodecSelector) (Track, error) {
|
||||
if err := d.Open(); err != nil {
|
||||
return nil, err
|
||||
@@ -339,7 +357,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) {
|
||||
@@ -365,7 +383,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)
|
||||
}
|
||||
@@ -425,7 +443,7 @@ func (track *AudioTrack) newEncodedReader(codecNames ...string) (EncodedReadClos
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sample := newAudioSampler(selectedCodec.ClockRate, inputProp.Latency)
|
||||
sample := newAudioSampler(selectedCodec.ClockRate, selectedCodec.Latency)
|
||||
|
||||
return &encodedReadCloserImpl{
|
||||
readFn: func() (EncodedBuffer, func(), error) {
|
||||
@@ -459,7 +477,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) {
|
||||
|
Reference in New Issue
Block a user