mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 04:46:10 +08:00
Compare commits
45 Commits
pcm16
...
fork-v0.3.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f5e42ede9c | ||
![]() |
0a1b59cbc3 | ||
![]() |
6e80ae05a6 | ||
![]() |
bb19442836 | ||
![]() |
96d6c18c1f | ||
![]() |
25525f97c0 | ||
![]() |
916b1c483f | ||
![]() |
524bf8faad | ||
![]() |
8a2dfcd5ba | ||
![]() |
8fce8a2bb5 | ||
![]() |
5b99500290 | ||
![]() |
e371c0d955 | ||
![]() |
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 |
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@@ -17,9 +17,9 @@ jobs:
|
||||
name: Linux Go ${{ matrix.go }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Install dependencies
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
libx264-dev
|
||||
- name: Run Test Suite
|
||||
run: make test
|
||||
- uses: codecov/codecov-action@v1
|
||||
- uses: codecov/codecov-action@v3
|
||||
if: matrix.go == '1.16'
|
||||
build-darwin:
|
||||
runs-on: macos-latest
|
||||
@@ -43,9 +43,9 @@ jobs:
|
||||
name: Darwin Go ${{ matrix.go }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Install dependencies
|
||||
@@ -62,9 +62,9 @@ jobs:
|
||||
name: Check Licenses
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.16'
|
||||
- name: Installing go-licenses
|
||||
|
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
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@
|
||||
|
||||
scripts/cross
|
||||
coverage.txt
|
||||
|
||||
.idea
|
||||
|
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})
|
@@ -13,6 +13,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM=
|
||||
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -21,6 +22,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -36,18 +38,19 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg=
|
||||
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10=
|
||||
github.com/pion/ice/v2 v2.0.16 h1:K6bzD8ef9vMKbGMTHaUweHXEyuNGnvr2zdqKoLKZPn0=
|
||||
github.com/pion/ice/v2 v2.0.16/go.mod h1:SJNJzC27gDZoOW0UoxIoC8Hf2PDxG28hQyNdSexDu38=
|
||||
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
|
||||
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
|
||||
github.com/pion/ice/v2 v2.1.7 h1:FjgDfUNrVYTxQabJrkBX6ld12tvYbgzHenqPh3PJF6E=
|
||||
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
|
||||
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
|
||||
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
@@ -60,6 +63,8 @@ github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
|
||||
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
|
||||
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
|
||||
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
@@ -69,17 +74,14 @@ github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
|
||||
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/webrtc/v3 v3.0.20 h1:Jj0sk45MqQdkR24E1wbFRmOzb1Lv258ot9zd2fYB/Pw=
|
||||
github.com/pion/webrtc/v3 v3.0.20/go.mod h1:0eJnCpQrUMpRnvyonw4ZiWClToerpixrZ2KcoTxvX9M=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -90,24 +92,32 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -116,19 +126,23 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -147,5 +161,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
46
examples/vnc/README.md
Normal file
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 {}
|
||||
}
|
17
go.mod
17
go.mod
@@ -2,14 +2,17 @@ module github.com/pion/mediadevices
|
||||
|
||||
go 1.13
|
||||
|
||||
replace github.com/pion/webrtc/v3 => github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f
|
||||
|
||||
require (
|
||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
|
||||
github.com/gen2brain/malgo v0.10.29
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
|
||||
github.com/gen2brain/malgo v0.10.35
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/rtp v1.6.5
|
||||
github.com/pion/webrtc/v3 v3.0.29
|
||||
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c
|
||||
github.com/pion/rtcp v1.2.9
|
||||
github.com/pion/rtp v1.7.13
|
||||
github.com/pion/webrtc/v3 v3.1.34
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
110
go.sum
110
go.sum
@@ -1,14 +1,14 @@
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
|
||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f h1:o7MCxR85nZxyOgjkmjtnXHsmPmchX3AEbWb/Bgpy3aI=
|
||||
github.com/EmrysMyrddin/webrtc/v3 v3.1.25-0.20220301142221-d92d68ff068f/go.mod h1:mO/yv7fBN3Lp7YNlnYcTj1jtpvNvssJG+7eh6itZ4xM=
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165 h1:QsIbRyO2tn5eSJZ/skuDqSTo0GWI5H4G1AT7Mm2H0Nw=
|
||||
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gen2brain/malgo v0.10.29 h1:bTYiUTUKJsEomNby+W0hgyLrOttUXIk4lTEnKA54iqM=
|
||||
github.com/gen2brain/malgo v0.10.29/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
||||
github.com/gen2brain/malgo v0.10.35 h1:D6aNo/Q0SnzQLHomTydTXxj4AJFdGJcVoE7I8JxPoUo=
|
||||
github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
@@ -19,15 +19,19 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0 h1:lICR2wyk9J6T709NawrhNTDi9DjMIbQqdlPT/EE0xBI=
|
||||
github.com/kbinani/screenshot v0.0.0-20210326165202-b96eb3309bb0/go.mod h1:ZceVWGtzUZmxyN+/1I+oG31oOm1dOA2QUNbua9TLVdE=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -40,50 +44,50 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
|
||||
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
|
||||
github.com/pion/ice/v2 v2.1.7 h1:FjgDfUNrVYTxQabJrkBX6ld12tvYbgzHenqPh3PJF6E=
|
||||
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
|
||||
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
|
||||
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/ice/v2 v2.2.1 h1:R3MeuJZpU1ty3diPqpD5OxaxcZ15eprAc+EtUiSoFxg=
|
||||
github.com/pion/ice/v2 v2.2.1/go.mod h1:Op8jlPtjeiycsXh93Cs4jK82C9j/kh7vef6ztIOvtIQ=
|
||||
github.com/pion/interceptor v0.1.7 h1:HThW0tIIKT9RRoDWGURe8rlZVOx0fJHxBHpA0ej0+bo=
|
||||
github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
|
||||
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
|
||||
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
|
||||
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
|
||||
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
|
||||
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
|
||||
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.0.29 h1:pVs6mYjbbYvC8pMsztayEz35DnUEFLPswsicGXaQjxo=
|
||||
github.com/pion/webrtc/v3 v3.0.29/go.mod h1:XFQeLYBf++bWWA0sJqh6zF1ouWluosxwTOMOoTZGaD0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
@@ -96,24 +100,23 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c h1:FRR4fGZm/CMwZka5baQ4z8c8StbxJOMjS/45e0BAxK0=
|
||||
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -128,13 +131,16 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
@@ -149,6 +155,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
24
ioreader.go
24
ioreader.go
@@ -1,5 +1,7 @@
|
||||
package mediadevices
|
||||
|
||||
import "github.com/pion/mediadevices/pkg/codec"
|
||||
|
||||
type EncodedBuffer struct {
|
||||
Data []byte
|
||||
Samples uint32
|
||||
@@ -8,11 +10,13 @@ type EncodedBuffer struct {
|
||||
type EncodedReadCloser interface {
|
||||
Read() (EncodedBuffer, func(), error)
|
||||
Close() error
|
||||
codec.Controllable
|
||||
}
|
||||
|
||||
type encodedReadCloserImpl struct {
|
||||
readFn func() (EncodedBuffer, func(), error)
|
||||
closeFn func() error
|
||||
readFn func() (EncodedBuffer, func(), error)
|
||||
closeFn func() error
|
||||
controllerFn func() codec.EncoderController
|
||||
}
|
||||
|
||||
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
|
||||
@@ -23,9 +27,14 @@ func (r *encodedReadCloserImpl) Close() error {
|
||||
return r.closeFn()
|
||||
}
|
||||
|
||||
func (r *encodedReadCloserImpl) Controller() codec.EncoderController {
|
||||
return r.controllerFn()
|
||||
}
|
||||
|
||||
type encodedIOReadCloserImpl struct {
|
||||
readFn func([]byte) (int, error)
|
||||
closeFn func() error
|
||||
readFn func([]byte) (int, error)
|
||||
closeFn func() error
|
||||
controller func() codec.EncoderController
|
||||
}
|
||||
|
||||
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
|
||||
@@ -48,7 +57,8 @@ func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserIm
|
||||
encoded.Data = encoded.Data[n:]
|
||||
return n, nil
|
||||
},
|
||||
closeFn: reader.Close,
|
||||
closeFn: reader.Close,
|
||||
controller: reader.Controller,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,3 +69,7 @@ func (r *encodedIOReadCloserImpl) Read(b []byte) (int, error) {
|
||||
func (r *encodedIOReadCloserImpl) Close() error {
|
||||
return r.closeFn()
|
||||
}
|
||||
|
||||
func (r *encodedIOReadCloserImpl) Controller() codec.EncoderController {
|
||||
return r.controller()
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -112,15 +112,37 @@ type VideoEncoderBuilder interface {
|
||||
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
|
||||
}
|
||||
|
||||
// ReadCloser is an io.ReadCloser with methods for rate limiting: SetBitRate and ForceKeyFrame
|
||||
// ReadCloser is an io.ReadCloser with a controller
|
||||
type ReadCloser interface {
|
||||
Read() (b []byte, release func(), err error)
|
||||
Close() error
|
||||
Controllable
|
||||
}
|
||||
|
||||
// EncoderController is the interface allowing to control the encoder behaviour after it's initialisation.
|
||||
// It will possibly have common control method in the future.
|
||||
// A controller can have optional methods represented by *Controller interfaces
|
||||
type EncoderController interface{}
|
||||
|
||||
// Controllable is a interface representing a encoder which can be controlled
|
||||
// after it's initialisation with an EncoderController
|
||||
type Controllable interface {
|
||||
Controller() EncoderController
|
||||
}
|
||||
|
||||
// KeyFrameController is a interface representing an encoder that can be forced to produce key frame on demand
|
||||
type KeyFrameController interface {
|
||||
EncoderController
|
||||
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
|
||||
ForceKeyFrame() error
|
||||
}
|
||||
|
||||
// BitRateController is a interface representing an encoder which can have a variable bit rate
|
||||
type BitRateController interface {
|
||||
EncoderController
|
||||
// SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
|
||||
// but this also means that the quality will also be lower.
|
||||
SetBitRate(int) error
|
||||
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
|
||||
ForceKeyFrame() error
|
||||
}
|
||||
|
||||
// BaseParams represents an codec's encoding properties
|
||||
|
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)
|
||||
}
|
||||
}
|
@@ -91,12 +91,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, err
|
||||
}
|
||||
|
||||
func (e *encoder) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
|
65
pkg/codec/mmal/mmal_test.go
Normal file
65
pkg/codec/mmal/mmal_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package mmal
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
t.Run("SimpleRead", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderSimpleReadTest(t, &p,
|
||||
prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 256,
|
||||
Height: 144,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
},
|
||||
image.NewYCbCr(
|
||||
image.Rect(0, 0, 256, 144),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
),
|
||||
)
|
||||
})
|
||||
t.Run("CloseTwice", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
FrameRate: 30,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
@@ -81,16 +81,15 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
|
||||
Slice payload = {0};
|
||||
|
||||
if(e->force_key_frame == 1) {
|
||||
info.eFrameType = videoFrameTypeI;
|
||||
e->engine->ForceIntraFrame(true);
|
||||
e->force_key_frame = 0;
|
||||
}
|
||||
|
||||
pic.iPicWidth = f.width;
|
||||
pic.iPicHeight = f.height;
|
||||
pic.iColorFormat = videoFormatI420;
|
||||
// We always received I420 format
|
||||
pic.iStride[0] = pic.iPicWidth;
|
||||
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
|
||||
pic.iStride[0] = f.ystride;
|
||||
pic.iStride[1] = pic.iStride[2] = f.cstride;
|
||||
pic.pData[0] = (unsigned char *)f.y;
|
||||
pic.pData[1] = (unsigned char *)f.u;
|
||||
pic.pData[2] = (unsigned char *)f.v;
|
||||
|
@@ -12,6 +12,8 @@ typedef struct Slice {
|
||||
|
||||
typedef struct Frame {
|
||||
void *y, *u, *v;
|
||||
int ystride;
|
||||
int cstride;
|
||||
int height;
|
||||
int width;
|
||||
} Frame;
|
||||
|
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)
|
||||
@@ -79,19 +81,23 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, nil
|
||||
}
|
||||
|
||||
func (e *encoder) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
e.engine.force_key_frame = C.int(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.closed = true
|
||||
|
||||
var rv C.int
|
||||
|
@@ -8,5 +8,6 @@ package openh264
|
||||
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a
|
||||
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-x64.a
|
||||
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-x64.a
|
||||
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-arm64.a
|
||||
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-windows-x64.a -lssp
|
||||
import "C"
|
||||
|
63
pkg/codec/openh264/openh264_test.go
Normal file
63
pkg/codec/openh264/openh264_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package openh264
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
t.Run("SimpleRead", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderSimpleReadTest(t, &p,
|
||||
prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 256,
|
||||
Height: 144,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
},
|
||||
image.NewYCbCr(
|
||||
image.Rect(0, 0, 256, 144),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
),
|
||||
)
|
||||
})
|
||||
t.Run("CloseTwice", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
FrameRate: 30,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
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.
@@ -121,11 +121,14 @@ func (e *encoder) SetBitRate(bitRate int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
if e.engine == nil {
|
||||
return nil
|
||||
}
|
||||
C.opus_encoder_destroy(e.engine)
|
||||
e.engine = nil
|
||||
return nil
|
||||
|
@@ -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"
|
||||
|
60
pkg/codec/opus/opus_test.go
Normal file
60
pkg/codec/opus/opus_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package opus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
"github.com/pion/mediadevices/pkg/wave"
|
||||
)
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
t.Run("SimpleRead", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.AudioEncoderSimpleReadTest(t, &p,
|
||||
prop.Media{
|
||||
Audio: prop.Audio{
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
wave.NewInt16Interleaved(wave.ChunkInfo{
|
||||
Len: 960,
|
||||
SamplingRate: 48000,
|
||||
Channels: 2,
|
||||
}),
|
||||
)
|
||||
})
|
||||
t.Run("CloseTwice", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.AudioEncoderCloseTwiceTest(t, &p, prop.Media{
|
||||
Audio: prop.Audio{
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
@@ -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
|
||||
@@ -540,18 +541,18 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, err
|
||||
}
|
||||
|
||||
func (e *encoderVP8) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
}
|
||||
|
||||
func (e *encoderVP8) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoderVP8) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
||||
C.vaDestroyContext(e.display, e.ctxID)
|
||||
C.vaDestroyConfig(e.display, e.confID)
|
||||
|
27
pkg/codec/vaapi/vp8_test.go
Normal file
27
pkg/codec/vaapi/vp8_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vaapi
|
||||
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoderVP8{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoderVP8{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vaapi
|
||||
@@ -475,18 +476,18 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, err
|
||||
}
|
||||
|
||||
func (e *encoderVP9) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
}
|
||||
|
||||
func (e *encoderVP9) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoderVP9) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
||||
C.vaDestroyContext(e.display, e.ctxID)
|
||||
C.vaDestroyConfig(e.display, e.confID)
|
||||
|
22
pkg/codec/vaapi/vp9_test.go
Normal file
22
pkg/codec/vaapi/vp9_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vaapi
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoderVP9{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement key frame control
|
||||
|
||||
e := &encoderVP9{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
@@ -295,10 +295,6 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, err
|
||||
}
|
||||
|
||||
func (e *encoder) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
@@ -306,10 +302,18 @@ func (e *encoder) ForceKeyFrame() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if e.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.closed = true
|
||||
|
||||
C.free(unsafe.Pointer(e.raw))
|
||||
|
@@ -7,11 +7,63 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
|
||||
"VP8": func() (codec.VideoEncoderBuilder, error) {
|
||||
p, err := NewVP8Params()
|
||||
return &p, err
|
||||
},
|
||||
"VP9": func() (codec.VideoEncoderBuilder, error) {
|
||||
p, err := NewVP9Params()
|
||||
p.LagInFrames = 0
|
||||
return &p, err
|
||||
},
|
||||
} {
|
||||
factory := factory
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("SimpleRead", func(t *testing.T) {
|
||||
p, err := factory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderSimpleReadTest(t, p,
|
||||
prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 256,
|
||||
Height: 144,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
},
|
||||
image.NewYCbCr(
|
||||
image.Rect(0, 0, 256, 144),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
),
|
||||
)
|
||||
})
|
||||
t.Run("CloseTwice", func(t *testing.T) {
|
||||
p, err := factory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
FrameRate: 30,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageSizeChange(t *testing.T) {
|
||||
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
|
||||
"VP8": func() (codec.VideoEncoderBuilder, error) {
|
||||
@@ -141,7 +193,7 @@ func TestRequestKeyFrame(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rel()
|
||||
r.ForceKeyFrame()
|
||||
r.Controller().(codec.KeyFrameController).ForceKeyFrame()
|
||||
_, rel, err = r.Read()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -158,3 +210,19 @@ func TestRequestKeyFrame(t *testing.T) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -124,12 +124,13 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
||||
return encoded, func() {}, err
|
||||
}
|
||||
|
||||
func (e *encoder) SetBitRate(b int) error {
|
||||
panic("SetBitRate is not implemented")
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
e.engine.force_key_frame = C.int(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) ForceKeyFrame() error {
|
||||
panic("ForceKeyFrame is not implemented")
|
||||
func (e *encoder) Controller() codec.EncoderController {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
|
65
pkg/codec/x264/x264_test.go
Normal file
65
pkg/codec/x264/x264_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package x264
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
t.Run("SimpleRead", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.BitRate = 200000
|
||||
codectest.VideoEncoderSimpleReadTest(t, &p,
|
||||
prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 256,
|
||||
Height: 144,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
},
|
||||
image.NewYCbCr(
|
||||
image.Rect(0, 0, 256, 144),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
),
|
||||
)
|
||||
})
|
||||
t.Run("CloseTwice", func(t *testing.T) {
|
||||
p, err := NewParams()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.BitRate = 200000
|
||||
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
FrameRate: 30,
|
||||
FrameFormat: frame.FormatI420,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||
t.SkipNow() // TODO: Implement bit rate control
|
||||
|
||||
e := &encoder{}
|
||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||
t.Error()
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/blackjack/webcam"
|
||||
@@ -133,6 +134,19 @@ func newCamera(path string) *camera {
|
||||
return c
|
||||
}
|
||||
|
||||
func getCameraReadTimeout() uint32 {
|
||||
// default to 5 seconds
|
||||
var readTimeoutSec uint32 = 5
|
||||
if val, ok := os.LookupEnv("PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"); ok {
|
||||
if valInt, err := strconv.Atoi(val); err == nil {
|
||||
if valInt > 0 {
|
||||
readTimeoutSec = uint32(valInt)
|
||||
}
|
||||
}
|
||||
}
|
||||
return readTimeoutSec
|
||||
}
|
||||
|
||||
func (c *camera) Open() error {
|
||||
cam, err := webcam.Open(c.path)
|
||||
if err != nil {
|
||||
@@ -179,12 +193,21 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.FrameRate > 0 {
|
||||
err = c.cam.SetFramerate(float32(p.FrameRate))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.cam.StartStreaming(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cam := c.cam
|
||||
|
||||
readTimeoutSec := getCameraReadTimeout()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c.cancel = cancel
|
||||
var buf []byte
|
||||
@@ -200,7 +223,7 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
||||
return nil, func() {}, io.EOF
|
||||
}
|
||||
|
||||
err := cam.WaitForFrame(5) // 5 seconds
|
||||
err := cam.WaitForFrame(readTimeoutSec)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
case *webcam.Timeout:
|
||||
|
@@ -70,3 +70,31 @@ func TestDiscover(t *testing.T) {
|
||||
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCameraReadTimeout(t *testing.T) {
|
||||
var expected uint32 = 5
|
||||
value := getCameraReadTimeout()
|
||||
if value != expected {
|
||||
t.Errorf("Expected: %d, got: %d", expected, value)
|
||||
}
|
||||
|
||||
envVarName := "PION_MEDIADEVICES_CAMERA_READ_TIMEOUT"
|
||||
os.Setenv(envVarName, "text")
|
||||
value = getCameraReadTimeout()
|
||||
if value != expected {
|
||||
t.Errorf("Expected: %d, got: %d", expected, value)
|
||||
}
|
||||
|
||||
os.Setenv(envVarName, "-1")
|
||||
value = getCameraReadTimeout()
|
||||
if value != expected {
|
||||
t.Errorf("Expected: %d, got: %d", expected, value)
|
||||
}
|
||||
|
||||
os.Setenv(envVarName, "1")
|
||||
expected = 1
|
||||
value = getCameraReadTimeout()
|
||||
if value != expected {
|
||||
t.Errorf("Expected: %d, got: %d", expected, value)
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
|
||||
|
15
rtpreader.go
15
rtpreader.go
@@ -1,15 +1,20 @@
|
||||
package mediadevices
|
||||
|
||||
import "github.com/pion/rtp"
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type RTPReadCloser interface {
|
||||
Read() (pkts []*rtp.Packet, release func(), err error)
|
||||
Close() error
|
||||
codec.Controllable
|
||||
}
|
||||
|
||||
type rtpReadCloserImpl struct {
|
||||
readFn func() ([]*rtp.Packet, func(), error)
|
||||
closeFn func() error
|
||||
readFn func() ([]*rtp.Packet, func(), error)
|
||||
closeFn func() error
|
||||
controllerFn func() codec.EncoderController
|
||||
}
|
||||
|
||||
func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
|
||||
@@ -19,3 +24,7 @@ func (r *rtpReadCloserImpl) Read() ([]*rtp.Packet, func(), error) {
|
||||
func (r *rtpReadCloserImpl) Close() error {
|
||||
return r.closeFn()
|
||||
}
|
||||
|
||||
func (r *rtpReadCloserImpl) Controller() codec.EncoderController {
|
||||
return r.controllerFn()
|
||||
}
|
||||
|
58
track.go
58
track.go
@@ -3,6 +3,7 @@ package mediadevices
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/pion/rtcp"
|
||||
"image"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -56,6 +57,8 @@ type Track interface {
|
||||
Kind() webrtc.RTPCodecType
|
||||
// StreamID is the group this track belongs too. This must be unique
|
||||
StreamID() string
|
||||
// RID is the RTP Stearm ID for this track. This is only used for Simulcast
|
||||
RID() string
|
||||
// Bind binds the current track source to the given peer connection. In Pion/webrtc v3, the bind
|
||||
// call will happen automatically after the SDP negotiation. Users won't need to call this manually.
|
||||
Bind(webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error)
|
||||
@@ -64,6 +67,8 @@ type Track interface {
|
||||
Unbind(webrtc.TrackLocalContext) error
|
||||
// NewRTPReader creates a new reader from the source. The reader will encode the source, and packetize
|
||||
// the encoded data in RTP format with given mtu size.
|
||||
//
|
||||
// Note: `mtu int` will be changed to `mtu uint16` in a future update.
|
||||
NewRTPReader(codecName string, ssrc uint32, mtu int) (RTPReadCloser, error)
|
||||
// NewEncodedReader creates a EncodedReadCloser that reads the encoded data in codecName format
|
||||
NewEncodedReader(codecName string) (EncodedReadCloser, error)
|
||||
@@ -113,6 +118,11 @@ func (track *baseTrack) StreamID() string {
|
||||
return generator.String()
|
||||
}
|
||||
|
||||
// RID is only relevant if you wish to use Simulcast
|
||||
func (track *baseTrack) RID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// OnEnded sets an error handler. When a track has been created and started, if an
|
||||
// error occurs, handler will get called with the error given to the parameter.
|
||||
func (track *baseTrack) OnEnded(handler func(error)) {
|
||||
@@ -148,6 +158,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
defer track.mu.Unlock()
|
||||
|
||||
signalCh := make(chan chan<- struct{})
|
||||
var stopRead chan struct{}
|
||||
track.activePeerConnections[ctx.ID()] = signalCh
|
||||
|
||||
var encodedReader RTPReadCloser
|
||||
@@ -173,6 +184,7 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
var doneCh chan<- struct{}
|
||||
writer := ctx.WriteStream()
|
||||
defer func() {
|
||||
close(stopRead)
|
||||
encodedReader.Close()
|
||||
|
||||
// When there's another call to unbind, it won't block since we mark the signalCh to be closed
|
||||
@@ -205,6 +217,37 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
||||
}
|
||||
}()
|
||||
|
||||
keyFrameController, ok := encodedReader.Controller().(codec.KeyFrameController)
|
||||
if ok {
|
||||
stopRead = make(chan struct{})
|
||||
go func() {
|
||||
reader := ctx.ReadStream()
|
||||
for {
|
||||
select {
|
||||
case <-stopRead:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
pkts, _, err := reader.ReadRTCP()
|
||||
if err != nil {
|
||||
track.onError(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pkt := range pkts {
|
||||
switch pkt.(type) {
|
||||
case *rtcp.PictureLossIndication, *rtcp.FullIntraRequest:
|
||||
if err := keyFrameController.ForceKeyFrame(); err != nil {
|
||||
track.onError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return selectedCodec, nil
|
||||
}
|
||||
|
||||
@@ -252,6 +295,7 @@ func newTrackFromDriver(d driver.Driver, constraints MediaTrackConstraints, sele
|
||||
type VideoTrack struct {
|
||||
*baseTrack
|
||||
*video.Broadcaster
|
||||
ShouldCopyFrames bool
|
||||
}
|
||||
|
||||
// NewVideoTrack constructs a new VideoTrack
|
||||
@@ -303,7 +347,7 @@ func (track *VideoTrack) Unbind(ctx webrtc.TrackLocalContext) error {
|
||||
}
|
||||
|
||||
func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadCloser, *codec.RTPCodec, error) {
|
||||
reader := track.NewReader(false)
|
||||
reader := track.NewReader(track.ShouldCopyFrames)
|
||||
inputProp, err := detectCurrentVideoProp(track.Broadcaster)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -325,7 +369,8 @@ func (track *VideoTrack) newEncodedReader(codecNames ...string) (EncodedReadClos
|
||||
}
|
||||
return buffer, release, err
|
||||
},
|
||||
closeFn: encodedReader.Close,
|
||||
closeFn: encodedReader.Close,
|
||||
controllerFn: encodedReader.Controller,
|
||||
}, selectedCodec, nil
|
||||
}
|
||||
|
||||
@@ -348,7 +393,7 @@ func (track *VideoTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packetizer := rtp.NewPacketizer(mtu, uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
|
||||
packetizer := rtp.NewPacketizer(uint16(mtu), uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
|
||||
|
||||
return &rtpReadCloserImpl{
|
||||
readFn: func() ([]*rtp.Packet, func(), error) {
|
||||
@@ -363,7 +408,8 @@ func (track *VideoTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
|
||||
pkts := packetizer.Packetize(encoded.Data, encoded.Samples)
|
||||
return pkts, release, err
|
||||
},
|
||||
closeFn: encodedReader.Close,
|
||||
closeFn: encodedReader.Close,
|
||||
controllerFn: encodedReader.Controller,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -374,7 +420,7 @@ type AudioTrack struct {
|
||||
*audio.Broadcaster
|
||||
}
|
||||
|
||||
// NewAudioTrack constructs a new VideoTrack
|
||||
// NewAudioTrack constructs a new AudioTrack
|
||||
func NewAudioTrack(source AudioSource, selector *CodecSelector) Track {
|
||||
return newAudioTrackFromReader(source, source, selector)
|
||||
}
|
||||
@@ -468,7 +514,7 @@ func (track *AudioTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (R
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packetizer := rtp.NewPacketizer(mtu, uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
|
||||
packetizer := rtp.NewPacketizer(uint16(mtu), uint8(selectedCodec.PayloadType), ssrc, selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate)
|
||||
|
||||
return &rtpReadCloserImpl{
|
||||
readFn: func() ([]*rtp.Packet, func(), error) {
|
||||
|
Reference in New Issue
Block a user