mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-28 13:22:14 +08:00
Compare commits
221 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
02d4e0e896 | ||
![]() |
38a3b829f6 | ||
![]() |
c4bb9eb649 | ||
![]() |
bc3bdc1855 | ||
![]() |
a6ffeb31ab | ||
![]() |
a9046e6ac1 | ||
![]() |
2bc011f39f | ||
![]() |
68df5b3eb5 | ||
![]() |
bf52b1af58 | ||
![]() |
ccee17d04c | ||
![]() |
f4f5a24ce4 | ||
![]() |
dec2300a95 | ||
![]() |
0c8f3cfc7a | ||
![]() |
6829d71e58 | ||
![]() |
1b36c0360d | ||
![]() |
5aa157e8fc | ||
![]() |
cd8e34f3ed | ||
![]() |
d03383a6fd | ||
![]() |
c4fd1de9a0 | ||
![]() |
47638e3290 | ||
![]() |
09b497727d | ||
![]() |
7ae82fbda7 | ||
![]() |
4477898296 | ||
![]() |
d5d41e9ca5 | ||
![]() |
9fb24fb036 | ||
![]() |
1cd1b136cc | ||
![]() |
08fb3e8a48 | ||
![]() |
4aae1bc842 | ||
![]() |
3c9fee958e | ||
![]() |
68092afe36 | ||
![]() |
cd92becd1c | ||
![]() |
2bad953124 | ||
![]() |
634addc04d | ||
![]() |
d0c1677cfb | ||
![]() |
b4770e5fbf | ||
![]() |
855441dad1 | ||
![]() |
2882fd42d5 | ||
![]() |
694b4abd83 | ||
![]() |
afb2f78e3c | ||
![]() |
b14ce7987c | ||
![]() |
728207526d | ||
![]() |
3582e5d017 | ||
![]() |
774b7de8d2 | ||
![]() |
96efc0932e | ||
![]() |
a1b4c0f69a | ||
![]() |
6bba5f1663 | ||
![]() |
79aef00e07 | ||
![]() |
03c44ee803 | ||
![]() |
dad145ef11 | ||
![]() |
72c1d7bb89 | ||
![]() |
fc301a8a92 | ||
![]() |
aca3ee9126 | ||
![]() |
4924411e88 | ||
![]() |
b88f541c4f | ||
![]() |
09f6bdeac4 | ||
![]() |
e64f0d8697 | ||
![]() |
36f908c6e2 | ||
![]() |
9987e01d3f | ||
![]() |
ae173b1b61 | ||
![]() |
4eea55285e | ||
![]() |
18cf1fe38a | ||
![]() |
138499b52d | ||
![]() |
87486146d5 | ||
![]() |
cadb155755 | ||
![]() |
2372f55064 | ||
![]() |
8568b1b20d | ||
![]() |
f0f6be7350 | ||
![]() |
8146e84f2f | ||
![]() |
4ff24bd656 | ||
![]() |
64dbe507f0 | ||
![]() |
dffaf0fcb4 | ||
![]() |
d3adaeea1a | ||
![]() |
7a414948c6 | ||
![]() |
e1616b8cc2 | ||
![]() |
52a080b55a | ||
![]() |
c2fe66c579 | ||
![]() |
ac50077e77 | ||
![]() |
2f5c61e1f3 | ||
![]() |
0715258726 | ||
![]() |
bccff100e5 | ||
![]() |
0507093a59 | ||
![]() |
bf290b026c | ||
![]() |
0dc4f43c94 | ||
![]() |
14bfaa5dbd | ||
![]() |
09c31a264c | ||
![]() |
30badd819d | ||
![]() |
dc8aeea11f | ||
![]() |
57c9ba0fc5 | ||
![]() |
b9ce5bb861 | ||
![]() |
e4ac96ea6b | ||
![]() |
11bf55f80c | ||
![]() |
55881ddd41 | ||
![]() |
62009a882b | ||
![]() |
dbd37689e4 | ||
![]() |
d561715bf9 | ||
![]() |
76ba048312 | ||
![]() |
5da0ebf443 | ||
![]() |
f8f8511d94 | ||
![]() |
85597da5bb | ||
![]() |
6ac1424488 | ||
![]() |
73a158d097 | ||
![]() |
cb23f1fa82 | ||
![]() |
a0f090dced | ||
![]() |
4f7542b614 | ||
![]() |
860f4a1490 | ||
![]() |
09f3bcc013 | ||
![]() |
f0ad407da8 | ||
![]() |
8d7ccada1c | ||
![]() |
2d9208de5b | ||
![]() |
52cf6e72b1 | ||
![]() |
7335797301 | ||
![]() |
3bec69bbf8 | ||
![]() |
58dc90d03a | ||
![]() |
8ad810e61e | ||
![]() |
6f204fa3d1 | ||
![]() |
5215057409 | ||
![]() |
3bcbed0286 | ||
![]() |
907e0d68e2 | ||
![]() |
9fd2d01dbe | ||
![]() |
285f8cd23c | ||
![]() |
b309c30ca0 | ||
![]() |
601f27c014 | ||
![]() |
2a04a14225 | ||
![]() |
416bbc33f3 | ||
![]() |
4a682a48c1 | ||
![]() |
14db2b8130 | ||
![]() |
a3c15d1fb0 | ||
![]() |
43272ea965 | ||
![]() |
e32fc1bdb8 | ||
![]() |
2af325d1a5 | ||
![]() |
5e0df5e5cf | ||
![]() |
d038133783 | ||
![]() |
cd6aaa1393 | ||
![]() |
82cc32308b | ||
![]() |
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 | ||
![]() |
23177a5d75 | ||
![]() |
3a04686875 | ||
![]() |
5f95b84719 | ||
![]() |
8919ba4fe5 | ||
![]() |
153c36e461 | ||
![]() |
10769b702e | ||
![]() |
2948735964 | ||
![]() |
ba848b3416 | ||
![]() |
94c6b66e46 | ||
![]() |
96fd92142c | ||
![]() |
d71b72c64d | ||
![]() |
8c2c8a9b27 | ||
![]() |
1e03f61b4b | ||
![]() |
b863c105c8 | ||
![]() |
6411b00e93 | ||
![]() |
acd2cb992b | ||
![]() |
dafd208de7 | ||
![]() |
fcec5a9149 | ||
![]() |
3d3830f7ff | ||
![]() |
655b513810 | ||
![]() |
eaaaacfc6b | ||
![]() |
fa95e47bad | ||
![]() |
020de77bc9 | ||
![]() |
33b6412c26 | ||
![]() |
f29d08ae6b | ||
![]() |
b5b0653697 | ||
![]() |
a1087f7f4e | ||
![]() |
217e634f7e | ||
![]() |
60b8e3ae1b | ||
![]() |
7df3114cdc | ||
![]() |
9741508d2b | ||
![]() |
7a569f0901 | ||
![]() |
ee40fcd070 | ||
![]() |
499c08d513 | ||
![]() |
5a1bd11087 | ||
![]() |
7ce935eac8 | ||
![]() |
a359005a7d | ||
![]() |
ca4116b5ce | ||
![]() |
8a4e0779d7 | ||
![]() |
d222ff3d74 | ||
![]() |
b9bb4fdc34 | ||
![]() |
cc823958e1 | ||
![]() |
64f39187b8 | ||
![]() |
c56cc487a3 | ||
![]() |
d86fc4a3e9 | ||
![]() |
2f21d9e738 | ||
![]() |
3316476b30 | ||
![]() |
0b1a19f343 | ||
![]() |
ad37c826b9 | ||
![]() |
d84d0a3b0c | ||
![]() |
c068f1176d |
111
.github/workflows/ci.yaml
vendored
111
.github/workflows/ci.yaml
vendored
@@ -13,13 +13,15 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go: [ '1.15', '1.14' ]
|
go:
|
||||||
|
- '1.20'
|
||||||
|
- '1.21'
|
||||||
name: Linux Go ${{ matrix.go }}
|
name: Linux Go ${{ matrix.go }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -30,54 +32,87 @@ jobs:
|
|||||||
libva-dev \
|
libva-dev \
|
||||||
libvpx-dev \
|
libvpx-dev \
|
||||||
libx264-dev
|
libx264-dev
|
||||||
- name: go vet
|
- name: Run Test Suite
|
||||||
run: go vet $(go list ./... | grep -v mmal)
|
run: make test
|
||||||
- name: go build
|
- uses: codecov/codecov-action@v3
|
||||||
run: go build $(go list ./... | grep -v mmal)
|
if: matrix.go == '1.21'
|
||||||
- name: go build without CGO
|
|
||||||
run: go build . pkg/...
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
- name: go test
|
|
||||||
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic $(go list ./... | grep -v mmal)
|
|
||||||
- uses: codecov/codecov-action@v1
|
|
||||||
if: matrix.go == '1.15'
|
|
||||||
- name: go test without CGO
|
|
||||||
run: go test . pkg/... -v
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
build-darwin:
|
build-darwin:
|
||||||
runs-on: macos-latest
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go: [ '1.15', '1.14' ]
|
runs-on:
|
||||||
name: Darwin Go ${{ matrix.go }}
|
- macos-latest
|
||||||
|
- ['self-hosted', 'macOS', 'ARM64']
|
||||||
|
go:
|
||||||
|
- '1.20'
|
||||||
|
- '1.21'
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
name: Darwin Go ${{ matrix.go }} ${{ join(matrix.runs-on, ' ') }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
# Set up local brew only on self-hosted darwin
|
||||||
|
- name: Checkout Homebrew
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: Homebrew/brew
|
||||||
|
path: homebrew
|
||||||
|
- name: Local brew cache key
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
id: brew-cache-key
|
||||||
|
run: echo "key=$(date +'%Y-%U')" | tee ${GITHUB_OUTPUT} # weekly update cache
|
||||||
|
- name: Cache local brew taps
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: homebrew/Library/Taps
|
||||||
|
key: ${{ runner.os }}-brew-taps-${{ steps.brew-cache-key.outputs.key }}
|
||||||
|
restore-keys: ${{ runner.os }}-brew-taps-
|
||||||
|
- name: Set up brew to install deps under temporary dir
|
||||||
|
if: matrix.runs-on != 'macos-latest'
|
||||||
|
run: |
|
||||||
|
dir="${GITHUB_WORKSPACE}/homebrew"
|
||||||
|
cd "${dir}"
|
||||||
|
|
||||||
|
echo "Set up shellenv" >&2
|
||||||
|
env="$(./bin/brew shellenv)"
|
||||||
|
echo "${env}" | tee -a ${GITHUB_ENV}
|
||||||
|
eval "${env}"
|
||||||
|
|
||||||
|
echo "Set up paths" >&2
|
||||||
|
echo "${dir}/bin" | tee -a ${GITHUB_PATH}
|
||||||
|
|
||||||
|
echo "Brew update" >&2
|
||||||
|
brew update --force --quiet
|
||||||
|
chmod -R go-w "$(brew --prefix)/share/zsh"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
which brew
|
||||||
brew install \
|
brew install \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
opus \
|
opus \
|
||||||
libvpx \
|
libvpx \
|
||||||
x264
|
x264
|
||||||
- name: go vet
|
- name: Run Test Suite
|
||||||
run: go vet $(go list ./... | grep -v mmal)
|
run: make test
|
||||||
- name: go build
|
check-licenses:
|
||||||
run: go build $(go list ./... | grep -v mmal)
|
runs-on: ubuntu-latest
|
||||||
- name: go build without CGO
|
name: Check Licenses
|
||||||
run: go build . pkg/...
|
steps:
|
||||||
env:
|
- name: Checkout
|
||||||
CGO_ENABLED: 0
|
uses: actions/checkout@v4
|
||||||
- name: go test
|
- name: Setup Go
|
||||||
run: go test -v -race $(go list ./... | grep -v mmal)
|
uses: actions/setup-go@v5
|
||||||
- name: go test without CGO
|
with:
|
||||||
run: go test . pkg/... -v
|
go-version: '1.21'
|
||||||
env:
|
- name: Installing go-licenses
|
||||||
CGO_ENABLED: 0
|
run: go install github.com/google/go-licenses@latest
|
||||||
|
- name: Checking licenses
|
||||||
|
run: go-licenses check ./...
|
||||||
|
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
2
.github/workflows/renovate-go-mod-fix.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: fix
|
- name: fix
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,3 +12,6 @@
|
|||||||
*.out
|
*.out
|
||||||
|
|
||||||
scripts/cross
|
scripts/cross
|
||||||
|
coverage.txt
|
||||||
|
|
||||||
|
.idea
|
||||||
|
83
Makefile
Normal file
83
Makefile
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
docker_owner := lherman
|
||||||
|
docker_prefix := cross
|
||||||
|
toolchain_dockerfiles := dockerfiles
|
||||||
|
script_path := $(realpath scripts)
|
||||||
|
toolchain_path := $(script_path)/$(docker_prefix)
|
||||||
|
os_list := \
|
||||||
|
linux \
|
||||||
|
windows \
|
||||||
|
darwin
|
||||||
|
arch_list := \
|
||||||
|
armv7 \
|
||||||
|
arm64 \
|
||||||
|
x64
|
||||||
|
supported_platforms := \
|
||||||
|
linux-armv7 \
|
||||||
|
linux-arm64 \
|
||||||
|
linux-x64 \
|
||||||
|
windows-x64 \
|
||||||
|
darwin-x64 \
|
||||||
|
darwin-arm64
|
||||||
|
cmd_build := build
|
||||||
|
cmd_test := test
|
||||||
|
examples_dir := examples
|
||||||
|
codec_dir := pkg/codec
|
||||||
|
codec_list := $(shell ls $(codec_dir)/*/Makefile)
|
||||||
|
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
|
||||||
|
targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms)))
|
||||||
|
pkgs_without_ext_device := $(shell go list ./... | grep -v mmal | grep -v vaapi)
|
||||||
|
pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
|
||||||
|
|
||||||
|
define BUILD_TEMPLATE
|
||||||
|
ifneq (,$$(findstring $(2)-$(3),$$(supported_platforms)))
|
||||||
|
$$(cmd_build)-$(1)-$(2)-$(3): toolchain-$(2)-$(3)
|
||||||
|
$$(MAKE) --directory=$$(codec_dir)/$(1) \
|
||||||
|
MEDIADEVICES_TOOLCHAIN_BIN=$$(toolchain_path)/$(docker_prefix)-$(2)-$(3) \
|
||||||
|
MEDIADEVICES_TARGET_PLATFORM=$(2)-$(3) \
|
||||||
|
MEDIADEVICES_TARGET_OS=$(2) \
|
||||||
|
MEDIADEVICES_TARGET_ARCH=$(3)
|
||||||
|
endif
|
||||||
|
endef
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: $(cmd_test) $(cmd_build)
|
||||||
|
|
||||||
|
# Subcommand:
|
||||||
|
# make build[-<codec_name>-<os>-<arch>]
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Build codec dependencies to multiple platforms.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# * make build: build all codecs for all supported platforms
|
||||||
|
# * make build-opus-darwin-x64: only build opus for darwin-x64 platform
|
||||||
|
$(cmd_build): $(targets)
|
||||||
|
|
||||||
|
toolchain-%: $(toolchain_dockerfiles)
|
||||||
|
$(MAKE) --directory=$< "$*" \
|
||||||
|
MEDIADEVICES_DOCKER_OWNER=$(docker_owner) \
|
||||||
|
MEDIADEVICES_DOCKER_PREFIX=$(docker_prefix)
|
||||||
|
@mkdir -p $(toolchain_path)
|
||||||
|
@docker run $(docker_owner)/$(docker_prefix)-$* > \
|
||||||
|
$(toolchain_path)/$(docker_prefix)-$*
|
||||||
|
@chmod +x $(toolchain_path)/$(docker_prefix)-$*
|
||||||
|
|
||||||
|
$(foreach codec, $(codec_list), \
|
||||||
|
$(foreach os, $(os_list), \
|
||||||
|
$(foreach arch, $(arch_list), \
|
||||||
|
$(eval $(call BUILD_TEMPLATE,$(codec),$(os),$(arch))))))
|
||||||
|
|
||||||
|
# Subcommand:
|
||||||
|
# make test
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# Run a series of tests
|
||||||
|
$(cmd_test):
|
||||||
|
go vet $(pkgs_without_ext_device)
|
||||||
|
go build $(pkgs_without_ext_device)
|
||||||
|
# go build without CGO
|
||||||
|
CGO_ENABLED=0 go build $(pkgs_without_cgo)
|
||||||
|
# go build with CGO
|
||||||
|
CGO_ENABLED=1 go build $(pkgs_without_ext_device)
|
||||||
|
$(MAKE) --directory=$(examples_dir)
|
||||||
|
go test -v -race -coverprofile=coverage.txt -covermode=atomic $(pkgs_without_ext_device)
|
69
README.md
69
README.md
@@ -6,8 +6,8 @@
|
|||||||
<h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4>
|
<h4 align="center">Go implementation of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">MediaDevices</a> API</h4>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||||
<a href="https://github.com/pion/mediadevices/actions"><img src="https://github.com/pion/mediadevices/workflows/CI/badge.svg?branch=master" alt="Build status"></a>
|
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/mediadevices/test.yaml">
|
||||||
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://godoc.org/github.com/pion/mediadevices?status.svg" alt="GoDoc"></a>
|
<a href="https://pkg.go.dev/github.com/pion/mediadevices"><img src="https://pkg.go.dev/badge/github.com/pion/mediadevices.svg" alt="Go Reference"></a>
|
||||||
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
<a href="https://codecov.io/gh/pion/mediadevices"><img src="https://codecov.io/gh/pion/mediadevices/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||||
</p>
|
</p>
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
|
|
||||||
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
|
`mediadevices` provides access to media input devices like cameras, microphones, and screen capture. It can also be used to encode your video/audio stream to various codec selections. `mediadevices` abstracts away the complexities of interacting with things like hardware and codecs allowing you to focus on building appilcations, interacting only with an amazingly simple, easy, and elegant API!
|
||||||
|
|
||||||
## Install
|
### Install
|
||||||
|
|
||||||
`go get -u github.com/pion/mediadevices`
|
```bash
|
||||||
|
go get -u github.com/pion/mediadevices
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
### Usage
|
||||||
|
|
||||||
The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
|
The following snippet shows how to capture a camera stream and store a frame as a jpeg image:
|
||||||
|
|
||||||
@@ -67,27 +69,23 @@ func main() {
|
|||||||
output, _ := os.Create("frame.jpg")
|
output, _ := os.Create("frame.jpg")
|
||||||
jpeg.Encode(output, frame, nil)
|
jpeg.Encode(output, frame, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## More Examples
|
### More Examples
|
||||||
|
|
||||||
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
|
* [Webrtc](/examples/webrtc) - Use Webrtc to create a realtime peer-to-peer video call
|
||||||
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
|
* [Face Detection](/examples/facedetection) - Use a machine learning algorithm to detect faces in a camera stream
|
||||||
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
|
* [RTP Stream](examples/rtp) - Capture camera stream, encode it in H264/VP8/VP9, and send it to a RTP server
|
||||||
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
|
* [HTTP Broadcast](/examples/http) - Broadcast camera stream through HTTP with MJPEG
|
||||||
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
|
* [Archive](/examples/archive) - Archive H264 encoded video stream from a camera
|
||||||
|
|
||||||
## Available Media Inputs
|
### Available Media Inputs
|
||||||
|
|
||||||
| Input | Linux | Mac | Windows |
|
| Input | Linux | Mac | Windows |
|
||||||
| :--------: | :---: | :-: | :-----: |
|
| :--------: | :---: | :-: | :-----: |
|
||||||
| Camera | ✔️ | ✔️ | ✔️ |
|
| Camera | ✔️ | ✔️ | ✔️ |
|
||||||
| Microphone | ✔️ | ✔️ | ✔️ |
|
| Microphone | ✔️ | ✔️ | ✔️ |
|
||||||
| Screen | ✔️ | ✔️ | ✔️ |
|
| Screen | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
By default, there's no media input registered. This decision was made to allow you to pay what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
|
By default, there's no media input registered. This decision was made to allow you to play only what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
@@ -96,8 +94,7 @@ import (
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Codecs
|
### Available Codecs
|
||||||
|
|
||||||
In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`:
|
In order to encode your video/audio, `mediadevices` needs to know what codecs that you want to use and their parameters. To do this, you need to import the associated packages for the codecs, and add them to the codec selector that you'll pass to `GetUserMedia`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -130,9 +127,9 @@ Since `mediadevices` doesn't implement the video/audio codecs, it needs to call
|
|||||||
|
|
||||||
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
|
Note: we do not provide recommendations on choosing one codec or another as it is very complex and can be subjective.
|
||||||
|
|
||||||
### Video Codecs
|
#### Video Codecs
|
||||||
|
|
||||||
#### x264
|
##### x264
|
||||||
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
|
A free software library and application for encoding video streams into the H.264/MPEG-4 AVC compression format.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
|
* Package: [github.com/pion/mediadevices/pkg/codec/x264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/x264)
|
||||||
@@ -140,19 +137,19 @@ A free software library and application for encoding video streams into the H.26
|
|||||||
* Mac: `brew install x264`
|
* Mac: `brew install x264`
|
||||||
* Ubuntu: `apt install libx264-dev`
|
* Ubuntu: `apt install libx264-dev`
|
||||||
|
|
||||||
#### mmal
|
##### mmal
|
||||||
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
|
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
|
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
|
||||||
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
|
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
|
||||||
|
|
||||||
#### openh264
|
##### openh264
|
||||||
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
|
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
|
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
|
||||||
* Installation: no installation needed, included as a static binary
|
* Installation: no installation needed, included as a static binary
|
||||||
|
|
||||||
#### vpx
|
##### vpx
|
||||||
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
|
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
|
* Package: [github.com/pion/mediadevices/pkg/codec/vpx](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vpx)
|
||||||
@@ -160,7 +157,7 @@ A free software video codec library from Google and the Alliance for Open Media
|
|||||||
* Mac: `brew install libvpx`
|
* Mac: `brew install libvpx`
|
||||||
* Ubuntu: `apt install libvpx-dev`
|
* Ubuntu: `apt install libvpx-dev`
|
||||||
|
|
||||||
#### vaapi
|
##### vaapi
|
||||||
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
|
An open source API that allows applications such as VLC media player or GStreamer to use hardware video acceleration capabilities (currently support VP8/VP9).
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
|
* Package: [github.com/pion/mediadevices/pkg/codec/vaapi](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/vaapi)
|
||||||
@@ -168,9 +165,9 @@ An open source API that allows applications such as VLC media player or GStreame
|
|||||||
* Ubuntu: `apt install libva-dev`
|
* Ubuntu: `apt install libva-dev`
|
||||||
|
|
||||||
|
|
||||||
### Audio Codecs
|
#### Audio Codecs
|
||||||
|
|
||||||
#### opus
|
##### opus
|
||||||
A totally open, royalty-free, highly versatile audio codec.
|
A totally open, royalty-free, highly versatile audio codec.
|
||||||
|
|
||||||
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
|
* Package: [github.com/pion/mediadevices/pkg/codec/opus](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/opus)
|
||||||
@@ -178,16 +175,14 @@ A totally open, royalty-free, highly versatile audio codec.
|
|||||||
* Mac: `brew install opus`
|
* Mac: `brew install opus`
|
||||||
* Ubuntu: `apt install libopus-dev`
|
* Ubuntu: `apt install libopus-dev`
|
||||||
|
|
||||||
## Benchmark
|
### Benchmark
|
||||||
|
|
||||||
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
|
Result as of Nov 4, 2020 with Go 1.14 on a Raspberry pi 3, `mediadevices` can produce video, encode, send across network, and decode at **720p, 30 fps with < 500 ms latency**.
|
||||||
|
|
||||||
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
|
The test was taken by capturing a camera stream, decoding the raw frames, encoding the video stream with mmal, and sending the stream through Webrtc.
|
||||||
|
|
||||||
## FAQ
|
### FAQ
|
||||||
|
|
||||||
### Failed to find the best driver that fits the constraints
|
|
||||||
|
|
||||||
|
#### Failed to find the best driver that fits the constraints
|
||||||
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
|
`mediadevices` provides an automated driver discovery through `GetUserMedia` and `GetDisplayMedia`. The driver discover algorithm works something like:
|
||||||
|
|
||||||
1. Open all registered drivers
|
1. Open all registered drivers
|
||||||
@@ -198,13 +193,14 @@ So, when `mediadevices` returns `failed to find the best driver that fits the co
|
|||||||
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
|
* Driver was not imported as a side effect in your program, e.g. `import _ github.com/pion/mediadevices/pkg/driver/camera`
|
||||||
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
|
* Your constraint is too strict that there's no driver can fullfil your requirements. In this case, you can try to turn up the debug level by specifying the following environment variable: `export PION_LOG_DEBUG=all` to see what was too strict and tune that.
|
||||||
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
|
* Your driver is not supported/implemented. In this case, you can either let us know (file an issue) and wait for the maintainers to implement it. Or, you can implement it yourself and register it through `RegisterDriverAdapter`
|
||||||
|
* If trying to use `import _ github.com/pion/mediadevices/pkg/driver/screen` note that you will need to use `GetDisplayMedia` instead of `GetUserMedia`
|
||||||
|
|
||||||
### Failed to find vpx/x264/mmal/opus codecs
|
#### Failed to find vpx/x264/mmal/opus codecs
|
||||||
|
|
||||||
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
|
Since `mediadevices` uses cgo to access video/audio codecs, it needs to find these libraries from the system. To accomplish this, [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) is used for library discovery.
|
||||||
|
|
||||||
If you see the following error message at compile time:
|
If you see the following error message at compile time:
|
||||||
```
|
|
||||||
|
```bash
|
||||||
# pkg-config --cflags -- vpx
|
# pkg-config --cflags -- vpx
|
||||||
Package vpx was not found in the pkg-config search path.
|
Package vpx was not found in the pkg-config search path.
|
||||||
Perhaps you should add the directory containing `vpx.pc'
|
Perhaps you should add the directory containing `vpx.pc'
|
||||||
@@ -218,8 +214,10 @@ There are 2 common problems:
|
|||||||
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
|
* The required codec library is not installed (vpx in this example). In this case, please refer to the [available codecs](#available-codecs).
|
||||||
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
|
* Pkg-config fails to find the `.pc` files for this codec ([reference](https://people.freedesktop.org/~dbn/pkg-config-guide.html#using)). In this case, you need to find where the codec library's `.pc` is stored, and let pkg-config knows with: `export PKG_CONFIG_PATH=/path/to/directory`.
|
||||||
|
|
||||||
|
### Roadmap
|
||||||
|
The library can be used with our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||||
|
|
||||||
## Community
|
### Community
|
||||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||||
|
|
||||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||||
@@ -227,11 +225,8 @@ Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and imp
|
|||||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||||
|
|
||||||
## Contributing
|
### Contributing
|
||||||
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
|
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
|
||||||
|
|
||||||
* [Lukas Herman](https://github.com/lherman-cs) - _Original Author_
|
### License
|
||||||
* [Atsushi Watanabe](https://github.com/at-wat) - _VP8, Screencast, etc._
|
|
||||||
|
|
||||||
## License
|
|
||||||
MIT License - see [LICENSE](LICENSE) for full text
|
MIT License - see [LICENSE](LICENSE) for full text
|
||||||
|
70
build.sh
70
build.sh
@@ -1,70 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
MEDIADEVICES_TOOLCHAIN_OWNER=lherman
|
|
||||||
MEDIADEVICES_TOOLCHAIN_PREFIX=cross
|
|
||||||
MEDIADEVICES_SCRIPT_PATH=$(realpath ./scripts)
|
|
||||||
MEDIADEVICES_TOOLCHAIN_PATH=${MEDIADEVICES_SCRIPT_PATH}/${MEDIADEVICES_TOOLCHAIN_PREFIX}
|
|
||||||
MEDIADEVICES_DOCKERFILES_PATH=dockerfiles
|
|
||||||
|
|
||||||
# Reference: https://github.com/dockcross/dockcross#cross-compilers
|
|
||||||
MEDIADEVICES_TARGET_PLATFORMS=(
|
|
||||||
linux-armv7
|
|
||||||
linux-arm64
|
|
||||||
linux-x64
|
|
||||||
windows-x64
|
|
||||||
darwin-x64
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -z ${VERBOSE} ]]; then
|
|
||||||
MEDIADEVICES_OUTPUT=/dev/null
|
|
||||||
else
|
|
||||||
MEDIADEVICES_OUTPUT=/dev/stdout
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_toolchains() {
|
|
||||||
bash ${MEDIADEVICES_DOCKERFILES_PATH}/build.sh &> ${MEDIADEVICES_OUTPUT}
|
|
||||||
for platform in ${MEDIADEVICES_TARGET_PLATFORMS[@]}
|
|
||||||
do
|
|
||||||
mkdir -p ${MEDIADEVICES_TOOLCHAIN_PATH}
|
|
||||||
image=${MEDIADEVICES_TOOLCHAIN_OWNER}/${MEDIADEVICES_TOOLCHAIN_PREFIX}-${platform}
|
|
||||||
bin_path=${MEDIADEVICES_TOOLCHAIN_PATH}/${MEDIADEVICES_TOOLCHAIN_PREFIX}-${platform}
|
|
||||||
docker run ${image} > ${bin_path}
|
|
||||||
chmod +x ${bin_path}
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
sub_builds=$(find pkg -type f -name "build.sh")
|
|
||||||
for sub_build in ${sub_builds[@]}
|
|
||||||
do
|
|
||||||
sub_build=$(realpath ${sub_build})
|
|
||||||
sub_build_dir=$(dirname ${sub_build})
|
|
||||||
current_dir=${PWD}
|
|
||||||
cd $sub_build_dir
|
|
||||||
for platform in ${MEDIADEVICES_TARGET_PLATFORMS[@]}
|
|
||||||
do
|
|
||||||
export MEDIADEVICES_TOOLCHAIN_BIN=${MEDIADEVICES_TOOLCHAIN_PATH}/${MEDIADEVICES_TOOLCHAIN_PREFIX}-${platform}
|
|
||||||
# convert '-' to '_' since '_' is more common in library names
|
|
||||||
export MEDIADEVICES_TARGET_PLATFORM=${platform//-/_}
|
|
||||||
export MEDIADEVICES_TARGET_OS=$(echo $MEDIADEVICES_TARGET_PLATFORM | cut -d'_' -f1)
|
|
||||||
export MEDIADEVICES_TARGET_ARCH=${platform//${MEDIADEVICES_TARGET_OS}-/}
|
|
||||||
|
|
||||||
echo "Building ${sub_build_dir}:"
|
|
||||||
echo " PLATFORM : ${MEDIADEVICES_TARGET_PLATFORM}"
|
|
||||||
echo " OS : ${MEDIADEVICES_TARGET_OS}"
|
|
||||||
echo " ARCH : ${MEDIADEVICES_TARGET_ARCH}"
|
|
||||||
echo " TOOLCHAIN_BIN : ${MEDIADEVICES_TOOLCHAIN_BIN}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
${sub_build} &> ${MEDIADEVICES_OUTPUT}
|
|
||||||
done
|
|
||||||
cd ${current_dir}
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ $# > 0 && $1 != "all" ]]; then
|
|
||||||
MEDIADEVICES_TARGET_PLATFORMS=($1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_toolchains
|
|
||||||
build
|
|
28
codec.go
28
codec.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CodecSelector is a container of video and audio encoder builders, which later will be used
|
// CodecSelector is a container of video and audio encoder builders, which later will be used
|
||||||
@@ -50,14 +50,15 @@ func NewCodecSelector(opts ...CodecSelectorOption) *CodecSelector {
|
|||||||
// Populate lets the webrtc engine be aware of supported codecs that are contained in CodecSelector
|
// Populate lets the webrtc engine be aware of supported codecs that are contained in CodecSelector
|
||||||
func (selector *CodecSelector) Populate(setting *webrtc.MediaEngine) {
|
func (selector *CodecSelector) Populate(setting *webrtc.MediaEngine) {
|
||||||
for _, encoder := range selector.videoEncoders {
|
for _, encoder := range selector.videoEncoders {
|
||||||
setting.RegisterCodec(encoder.RTPCodec().RTPCodec)
|
setting.RegisterCodec(encoder.RTPCodec().RTPCodecParameters, webrtc.RTPCodecTypeVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, encoder := range selector.audioEncoders {
|
for _, encoder := range selector.audioEncoders {
|
||||||
setting.RegisterCodec(encoder.RTPCodec().RTPCodec)
|
setting.RegisterCodec(encoder.RTPCodec().RTPCodecParameters, webrtc.RTPCodecTypeAudio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selectVideoCodecByNames selects a single codec that can be built and matched. codecNames can be formatted as "video/<codecName>" or "<codecName>"
|
||||||
func (selector *CodecSelector) selectVideoCodecByNames(reader video.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
|
func (selector *CodecSelector) selectVideoCodecByNames(reader video.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
|
||||||
var selectedEncoder codec.VideoEncoderBuilder
|
var selectedEncoder codec.VideoEncoderBuilder
|
||||||
var encodedReader codec.ReadCloser
|
var encodedReader codec.ReadCloser
|
||||||
@@ -66,8 +67,10 @@ func (selector *CodecSelector) selectVideoCodecByNames(reader video.Reader, inpu
|
|||||||
|
|
||||||
outer:
|
outer:
|
||||||
for _, wantCodec := range codecNames {
|
for _, wantCodec := range codecNames {
|
||||||
|
wantCodecLower := strings.ToLower(wantCodec)
|
||||||
for _, encoder := range selector.videoEncoders {
|
for _, encoder := range selector.videoEncoders {
|
||||||
if encoder.RTPCodec().Name == wantCodec {
|
// MimeType is formated as "video/<codecName>"
|
||||||
|
if strings.HasSuffix(strings.ToLower(encoder.RTPCodec().MimeType), wantCodecLower) {
|
||||||
encodedReader, err = encoder.BuildVideoEncoder(reader, inputProp)
|
encodedReader, err = encoder.BuildVideoEncoder(reader, inputProp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
selectedEncoder = encoder
|
selectedEncoder = encoder
|
||||||
@@ -75,7 +78,7 @@ outer:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().Name, err))
|
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().MimeType, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +89,17 @@ outer:
|
|||||||
return encodedReader, selectedEncoder.RTPCodec(), nil
|
return encodedReader, selectedEncoder.RTPCodec(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selector *CodecSelector) selectVideoCodec(reader video.Reader, inputProp prop.Media, codecs ...*webrtc.RTPCodec) (codec.ReadCloser, *codec.RTPCodec, error) {
|
func (selector *CodecSelector) selectVideoCodec(reader video.Reader, inputProp prop.Media, codecs ...webrtc.RTPCodecParameters) (codec.ReadCloser, *codec.RTPCodec, error) {
|
||||||
var codecNames []string
|
var codecNames []string
|
||||||
|
|
||||||
for _, codec := range codecs {
|
for _, codec := range codecs {
|
||||||
codecNames = append(codecNames, codec.Name)
|
codecNames = append(codecNames, codec.MimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selector.selectVideoCodecByNames(reader, inputProp, codecNames...)
|
return selector.selectVideoCodecByNames(reader, inputProp, codecNames...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selectAudioCodecByNames selects a single codec that can be built and matched. codecNames can be formatted as "audio/<codecName>" or "<codecName>"
|
||||||
func (selector *CodecSelector) selectAudioCodecByNames(reader audio.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
|
func (selector *CodecSelector) selectAudioCodecByNames(reader audio.Reader, inputProp prop.Media, codecNames ...string) (codec.ReadCloser, *codec.RTPCodec, error) {
|
||||||
var selectedEncoder codec.AudioEncoderBuilder
|
var selectedEncoder codec.AudioEncoderBuilder
|
||||||
var encodedReader codec.ReadCloser
|
var encodedReader codec.ReadCloser
|
||||||
@@ -104,8 +108,10 @@ func (selector *CodecSelector) selectAudioCodecByNames(reader audio.Reader, inpu
|
|||||||
|
|
||||||
outer:
|
outer:
|
||||||
for _, wantCodec := range codecNames {
|
for _, wantCodec := range codecNames {
|
||||||
|
wantCodecLower := strings.ToLower(wantCodec)
|
||||||
for _, encoder := range selector.audioEncoders {
|
for _, encoder := range selector.audioEncoders {
|
||||||
if encoder.RTPCodec().Name == wantCodec {
|
// MimeType is formated as "audio/<codecName>"
|
||||||
|
if strings.HasSuffix(strings.ToLower(encoder.RTPCodec().MimeType), wantCodecLower) {
|
||||||
encodedReader, err = encoder.BuildAudioEncoder(reader, inputProp)
|
encodedReader, err = encoder.BuildAudioEncoder(reader, inputProp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
selectedEncoder = encoder
|
selectedEncoder = encoder
|
||||||
@@ -113,7 +119,7 @@ outer:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().Name, err))
|
errReasons = append(errReasons, fmt.Sprintf("%s: %s", encoder.RTPCodec().MimeType, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,11 +130,11 @@ outer:
|
|||||||
return encodedReader, selectedEncoder.RTPCodec(), nil
|
return encodedReader, selectedEncoder.RTPCodec(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selector *CodecSelector) selectAudioCodec(reader audio.Reader, inputProp prop.Media, codecs ...*webrtc.RTPCodec) (codec.ReadCloser, *codec.RTPCodec, error) {
|
func (selector *CodecSelector) selectAudioCodec(reader audio.Reader, inputProp prop.Media, codecs ...webrtc.RTPCodecParameters) (codec.ReadCloser, *codec.RTPCodec, error) {
|
||||||
var codecNames []string
|
var codecNames []string
|
||||||
|
|
||||||
for _, codec := range codecs {
|
for _, codec := range codecs {
|
||||||
codecNames = append(codecNames, codec.Name)
|
codecNames = append(codecNames, codec.MimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selector.selectAudioCodecByNames(reader, inputProp, codecNames...)
|
return selector.selectAudioCodecByNames(reader, inputProp, codecNames...)
|
||||||
|
11
dockerfiles/Makefile
Normal file
11
dockerfiles/Makefile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
dockerfiles := $(wildcard *.Dockerfile)
|
||||||
|
supported_platforms := $(dockerfiles:.Dockerfile=)
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: $(supported_platforms)
|
||||||
|
|
||||||
|
%: %.Dockerfile guard-MEDIADEVICES_DOCKER_OWNER guard-MEDIADEVICES_DOCKER_PREFIX
|
||||||
|
docker build -t "$(MEDIADEVICES_DOCKER_OWNER)/$(MEDIADEVICES_DOCKER_PREFIX)-$@" -f "$<" .
|
||||||
|
|
||||||
|
guard-%:
|
||||||
|
@if [ -z ${$*} ]; then echo "$* is a required environment variable"; exit 1; fi
|
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cd $(dirname $0)
|
|
||||||
|
|
||||||
OWNER=lherman
|
|
||||||
PREFIX=cross
|
|
||||||
IMAGES=$(ls *.Dockerfile)
|
|
||||||
|
|
||||||
for image in ${IMAGES[@]}
|
|
||||||
do
|
|
||||||
tag=${OWNER}/cross-${image//.Dockerfile/}
|
|
||||||
docker build -t "${tag}" -f "$image" .
|
|
||||||
done
|
|
47
dockerfiles/darwin-arm64.Dockerfile
Normal file
47
dockerfiles/darwin-arm64.Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
FROM dockercore/golang-cross as m1cross
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update -qq && apt-get install -y -q --no-install-recommends \
|
||||||
|
cmake \
|
||||||
|
git \
|
||||||
|
libssl-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libz-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV SDK_VERSION=11.3 \
|
||||||
|
TARGET_DIR=/osxcross/target \
|
||||||
|
UNATTENDED=1
|
||||||
|
|
||||||
|
WORKDIR /work
|
||||||
|
RUN git clone --depth=1 https://github.com/tpoechtrager/osxcross.git /work \
|
||||||
|
&& cd /work/tarballs \
|
||||||
|
&& wget -q https://github.com/phracker/MacOSX-SDKs/releases/download/${SDK_VERSION}/MacOSX${SDK_VERSION}.sdk.tar.xz
|
||||||
|
|
||||||
|
# Build cross compile toolchain for Apple silicon
|
||||||
|
RUN ./build.sh
|
||||||
|
|
||||||
|
|
||||||
|
FROM dockcross/base
|
||||||
|
|
||||||
|
ENV OSX_CROSS_PATH=/osxcross
|
||||||
|
|
||||||
|
COPY --from=m1cross "${OSX_CROSS_PATH}/." "${OSX_CROSS_PATH}/"
|
||||||
|
ENV PATH=${OSX_CROSS_PATH}/target/bin:$PATH
|
||||||
|
|
||||||
|
COPY init.sh /tmp/init.sh
|
||||||
|
RUN bash /tmp/init.sh
|
||||||
|
|
||||||
|
ENV CC=arm64-apple-darwin20.4-clang \
|
||||||
|
CXX=arm64-apple-darwin20.4-clang++ \
|
||||||
|
CPP=arm64-apple-darwin20.4-clang++ \
|
||||||
|
AR=arm64-apple-darwin20.4-ar \
|
||||||
|
AS=arm64-apple-darwin20.4-as \
|
||||||
|
LD=arm64-apple-darwin20.4-ld
|
||||||
|
|
||||||
|
COPY darwin-arm64.cmake ${OSX_CROSS_PATH}/
|
||||||
|
ENV CMAKE_TOOLCHAIN_FILE ${OSX_CROSS_PATH}/darwin-arm64.cmake
|
||||||
|
|
||||||
|
ARG IMAGE=lherman/cross-darwin-arm64
|
||||||
|
ARG VERSION=latest
|
||||||
|
ENV DEFAULT_DOCKCROSS_IMAGE ${IMAGE}:${VERSION}
|
8
dockerfiles/darwin-arm64.cmake
Normal file
8
dockerfiles/darwin-arm64.cmake
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Darwin)
|
||||||
|
set(CMAKE_SYSTEM_VERSION 1)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR arm64)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER $ENV{CC})
|
||||||
|
set(CMAKE_CXX_COMPILER $ENV{CXX})
|
||||||
|
set(CMAKE_AR $ENV{AR})
|
||||||
|
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
|
1
examples/.gitignore
vendored
1
examples/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
go.sum
|
|
8
examples/Makefile
Normal file
8
examples/Makefile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
examples := $(shell find * -maxdepth 0 -type d)
|
||||||
|
examples := $(filter-out internal,$(examples))
|
||||||
|
|
||||||
|
.PHONY: all $(examples)
|
||||||
|
all: $(examples)
|
||||||
|
|
||||||
|
$(examples):
|
||||||
|
cd $@ && go build -mod=mod
|
1
examples/archive/.gitignore
vendored
Normal file
1
examples/archive/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
archive
|
Binary file not shown.
@@ -43,7 +43,7 @@ func main() {
|
|||||||
|
|
||||||
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
|
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
|
||||||
c.Width = prop.Int(640)
|
c.Width = prop.Int(640)
|
||||||
c.Height = prop.Int(480)
|
c.Height = prop.Int(480)
|
||||||
},
|
},
|
||||||
@@ -68,7 +68,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
reader, err := videoTrack.NewEncodedReader(x264Params.RTPCodec().Name)
|
reader, err := videoTrack.NewEncodedIOReader(x264Params.RTPCodec().MimeType)
|
||||||
must(err)
|
must(err)
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
|
1
examples/facedetection/.gitignore
vendored
Normal file
1
examples/facedetection/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
facedetection
|
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
pigo "github.com/esimov/pigo/core"
|
pigo "github.com/esimov/pigo/core"
|
||||||
"github.com/pion/mediadevices"
|
"github.com/pion/mediadevices"
|
||||||
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func main() {
|
|||||||
|
|
||||||
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = prop.FrameFormatExact(frame.FormatUYVY)
|
c.FrameFormat = prop.FrameFormatOneOf{frame.FormatI420, frame.FormatYUY2}
|
||||||
c.Width = prop.Int(640)
|
c.Width = prop.Int(640)
|
||||||
c.Height = prop.Int(480)
|
c.Height = prop.Int(480)
|
||||||
},
|
},
|
||||||
@@ -97,7 +97,7 @@ func main() {
|
|||||||
frame, release, err := videoReader.Read()
|
frame, release, err := videoReader.Read()
|
||||||
must(err)
|
must(err)
|
||||||
|
|
||||||
// Since we asked the frame format to be exactly YUY2 in GetUserMedia, we can guarantee that it must be YCbCr
|
// Since we asked the frame format to be exactly I420/YUY2 in GetUserMedia, we can guarantee that it must be YCbCr
|
||||||
if detectFace(frame.(*image.YCbCr)) {
|
if detectFace(frame.(*image.YCbCr)) {
|
||||||
log.Println("Detect a face")
|
log.Println("Detect a face")
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,10 @@ module github.com/pion/mediadevices/examples
|
|||||||
|
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
// Please don't commit require entries of examples.
|
require (
|
||||||
// `git checkout master examples/go.mod` to revert this file.
|
github.com/esimov/pigo v1.4.6
|
||||||
require github.com/pion/mediadevices v0.0.0
|
github.com/pion/mediadevices v0.0.0
|
||||||
|
github.com/pion/webrtc/v3 v3.2.29
|
||||||
|
)
|
||||||
|
|
||||||
replace github.com/pion/mediadevices v0.0.0 => ../
|
replace github.com/pion/mediadevices v0.0.0 => ../
|
||||||
|
225
examples/go.sum
Normal file
225
examples/go.sum
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
github.com/blackjack/webcam v0.5.0 h1:NImYpsAbWxglejopcQGQ3FClxhJZaBBgr5fV7nsGdvk=
|
||||||
|
github.com/blackjack/webcam v0.5.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
|
||||||
|
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
|
||||||
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
|
||||||
|
github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
|
||||||
|
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/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/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||||
|
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/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.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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
|
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||||
|
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||||
|
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||||
|
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||||
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
|
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||||
|
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||||
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
|
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
|
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
||||||
|
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
|
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
|
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||||
|
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
|
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||||
|
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||||
|
github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
|
||||||
|
github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
||||||
|
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||||
|
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||||
|
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||||
|
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||||
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
|
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||||
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
|
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||||
|
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||||
|
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||||
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
|
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||||
|
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-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-20201107080550-4d91cf3a1aaf/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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
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=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
1
examples/http/.gitignore
vendored
Normal file
1
examples/http/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
http
|
1
examples/rtp/.gitignore
vendored
Normal file
1
examples/rtp/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rtp
|
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ func main() {
|
|||||||
|
|
||||||
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
|
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
|
||||||
c.Width = prop.Int(640)
|
c.Width = prop.Int(640)
|
||||||
c.Height = prop.Int(480)
|
c.Height = prop.Int(480)
|
||||||
},
|
},
|
||||||
@@ -51,7 +52,7 @@ func main() {
|
|||||||
videoTrack := mediaStream.GetVideoTracks()[0]
|
videoTrack := mediaStream.GetVideoTracks()[0]
|
||||||
defer videoTrack.Close()
|
defer videoTrack.Close()
|
||||||
|
|
||||||
rtpReader, err := videoTrack.NewRTPReader(x264Params.RTPCodec().Name, mtu)
|
rtpReader, err := videoTrack.NewRTPReader(x264Params.RTPCodec().MimeType, rand.Uint32(), mtu)
|
||||||
must(err)
|
must(err)
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", dest)
|
addr, err := net.ResolveUDPAddr("udp", dest)
|
||||||
|
1
examples/vnc/.gitignore
vendored
Normal file
1
examples/vnc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
vnc
|
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 {}
|
||||||
|
}
|
1
examples/webrtc/.gitignore
vendored
Normal file
1
examples/webrtc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
webrtc
|
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/examples/internal/signal"
|
"github.com/pion/mediadevices/examples/internal/signal"
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v3"
|
||||||
|
|
||||||
// If you don't like x264, you can also use vpx by importing as below
|
// 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
|
// "github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
|
||||||
@@ -57,10 +57,7 @@ func main() {
|
|||||||
|
|
||||||
mediaEngine := webrtc.MediaEngine{}
|
mediaEngine := webrtc.MediaEngine{}
|
||||||
codecSelector.Populate(&mediaEngine)
|
codecSelector.Populate(&mediaEngine)
|
||||||
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
|
api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine))
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
|
|
||||||
peerConnection, err := api.NewPeerConnection(config)
|
peerConnection, err := api.NewPeerConnection(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -74,7 +71,7 @@ func main() {
|
|||||||
|
|
||||||
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
|
||||||
Video: func(c *mediadevices.MediaTrackConstraints) {
|
Video: func(c *mediadevices.MediaTrackConstraints) {
|
||||||
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
|
c.FrameFormat = prop.FrameFormat(frame.FormatI420)
|
||||||
c.Width = prop.Int(640)
|
c.Width = prop.Int(640)
|
||||||
c.Height = prop.Int(480)
|
c.Height = prop.Int(480)
|
||||||
},
|
},
|
||||||
@@ -86,19 +83,13 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tracker := range s.GetTracks() {
|
for _, track := range s.GetTracks() {
|
||||||
tracker.OnEnded(func(err error) {
|
track.OnEnded(func(err error) {
|
||||||
fmt.Printf("Track (ID: %s) ended with error: %v\n",
|
fmt.Printf("Track (ID: %s) ended with error: %v\n",
|
||||||
tracker.ID(), err)
|
track.ID(), err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// In Pion/webrtc v3, bind will be called automatically after SDP negotiation
|
_, err = peerConnection.AddTransceiverFromTrack(track,
|
||||||
webrtcTrack, err := tracker.Bind(peerConnection)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = peerConnection.AddTransceiverFromTrack(webrtcTrack,
|
|
||||||
webrtc.RtpTransceiverInit{
|
webrtc.RtpTransceiverInit{
|
||||||
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
||||||
},
|
},
|
||||||
@@ -120,13 +111,23 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create channel that is blocked until ICE Gathering is complete
|
||||||
|
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
|
||||||
|
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
err = peerConnection.SetLocalDescription(answer)
|
err = peerConnection.SetLocalDescription(answer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
// Output the answer in base64 so we can paste it in browser
|
||||||
fmt.Println(signal.Encode(answer))
|
fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
|
||||||
|
|
||||||
|
// Block forever
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
47
go.mod
47
go.mod
@@ -1,18 +1,41 @@
|
|||||||
module github.com/pion/mediadevices
|
module github.com/pion/mediadevices
|
||||||
|
|
||||||
go 1.13
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
github.com/blackjack/webcam v0.5.0
|
||||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
|
github.com/gen2brain/malgo v0.11.21
|
||||||
github.com/gen2brain/malgo v0.10.27
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
|
||||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
github.com/pion/interceptor v0.1.25
|
||||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 // indirect
|
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/rtp v1.6.2
|
github.com/pion/rtcp v1.2.14
|
||||||
github.com/pion/webrtc/v2 v2.2.26
|
github.com/pion/rtp v1.8.3
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/pion/webrtc/v3 v3.2.29
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect
|
golang.org/x/image v0.15.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
|
||||||
|
github.com/jezek/xgb v1.1.0 // indirect
|
||||||
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||||
|
github.com/pion/datachannel v1.5.5 // indirect
|
||||||
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
|
github.com/pion/ice/v2 v2.3.13 // indirect
|
||||||
|
github.com/pion/mdns v0.0.12 // indirect
|
||||||
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
|
github.com/pion/sctp v1.8.12 // indirect
|
||||||
|
github.com/pion/sdp/v3 v3.0.8 // indirect
|
||||||
|
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||||
|
github.com/pion/stun v0.6.1 // indirect
|
||||||
|
github.com/pion/transport/v2 v2.2.3 // indirect
|
||||||
|
github.com/pion/turn/v2 v2.1.3 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
|
golang.org/x/net v0.20.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
280
go.sum
280
go.sum
@@ -1,138 +1,218 @@
|
|||||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd h1:u7K2oMFMd8APDV3fM1j2rO3U/XJf1g1qC3DDTKou8iM=
|
github.com/blackjack/webcam v0.5.0 h1:NImYpsAbWxglejopcQGQ3FClxhJZaBBgr5fV7nsGdvk=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/blackjack/webcam v0.5.0/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
|
||||||
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/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gen2brain/malgo v0.10.27 h1:KlNitZIO8V4W2VnjtTM8AGMy/XBb2pN+fnIB5bEps8E=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gen2brain/malgo v0.10.27/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
|
github.com/gen2brain/malgo v0.11.21 h1:qsS4Dh6zhZgmvAW5CtKRxDjQzHbc2NJlBG9eE0tgS8w=
|
||||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
github.com/gen2brain/malgo v0.11.21/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
|
||||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
|
||||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/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/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
|
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
|
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
|
||||||
|
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 h1:4BxFx5XCtXc+nFtXDGDW+Uu5sPtsAbvPh6RObj3fG9o=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
|
||||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
github.com/pion/dtls/v2 v2.0.2 h1:FHCHTiM182Y8e15aFTiORroiATUI16ryHiQh8AIOJ1E=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/pion/ice v0.7.18 h1:KbAWlzWRUdX9SmehBh3gYpIFsirjhSQsCw6K2MjYMK0=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/ice v0.7.18/go.mod h1:+Bvnm3nYC6Nnp7VV6glUkuOfToB/AtMRZpOU8ihuf4c=
|
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||||
|
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||||
|
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||||
|
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||||
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
|
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||||
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
|
|
||||||
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
|
|
||||||
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA=
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
|
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
|
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
||||||
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
|
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
|
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||||
github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8=
|
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
|
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||||
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
|
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||||
github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw=
|
github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0=
|
||||||
github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
|
github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||||
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
|
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||||
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
|
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
|
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||||
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
|
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||||
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
github.com/pion/webrtc/v2 v2.2.26 h1:01hWE26pL3LgqfxvQ1fr6O4ZtyRFFJmQEZK39pHWfFc=
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc=
|
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/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-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
14
ioreader.go
14
ioreader.go
@@ -1,5 +1,7 @@
|
|||||||
package mediadevices
|
package mediadevices
|
||||||
|
|
||||||
|
import "github.com/pion/mediadevices/pkg/codec"
|
||||||
|
|
||||||
type EncodedBuffer struct {
|
type EncodedBuffer struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
Samples uint32
|
Samples uint32
|
||||||
@@ -8,11 +10,13 @@ type EncodedBuffer struct {
|
|||||||
type EncodedReadCloser interface {
|
type EncodedReadCloser interface {
|
||||||
Read() (EncodedBuffer, func(), error)
|
Read() (EncodedBuffer, func(), error)
|
||||||
Close() error
|
Close() error
|
||||||
|
codec.Controllable
|
||||||
}
|
}
|
||||||
|
|
||||||
type encodedReadCloserImpl struct {
|
type encodedReadCloserImpl struct {
|
||||||
readFn func() (EncodedBuffer, func(), error)
|
readFn func() (EncodedBuffer, func(), error)
|
||||||
closeFn func() error
|
closeFn func() error
|
||||||
|
controllerFn func() codec.EncoderController
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
|
func (r *encodedReadCloserImpl) Read() (EncodedBuffer, func(), error) {
|
||||||
@@ -23,9 +27,14 @@ func (r *encodedReadCloserImpl) Close() error {
|
|||||||
return r.closeFn()
|
return r.closeFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *encodedReadCloserImpl) Controller() codec.EncoderController {
|
||||||
|
return r.controllerFn()
|
||||||
|
}
|
||||||
|
|
||||||
type encodedIOReadCloserImpl struct {
|
type encodedIOReadCloserImpl struct {
|
||||||
readFn func([]byte) (int, error)
|
readFn func([]byte) (int, error)
|
||||||
closeFn func() error
|
closeFn func() error
|
||||||
|
controller func() codec.EncoderController
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
|
func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserImpl {
|
||||||
@@ -49,6 +58,7 @@ func newEncodedIOReadCloserImpl(reader EncodedReadCloser) *encodedIOReadCloserIm
|
|||||||
return n, nil
|
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 {
|
func (r *encodedIOReadCloserImpl) Close() error {
|
||||||
return r.closeFn()
|
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"
|
||||||
_ "github.com/pion/mediadevices/pkg/driver/audiotest"
|
_ "github.com/pion/mediadevices/pkg/driver/audiotest"
|
||||||
_ "github.com/pion/mediadevices/pkg/driver/videotest"
|
_ "github.com/pion/mediadevices/pkg/driver/videotest"
|
||||||
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,20 +98,58 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
|
|||||||
t.Fatal("expect to get at least 1 property")
|
t.Fatal("expect to get at least 1 property")
|
||||||
}
|
}
|
||||||
expectedProp := driver.Properties()[0]
|
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{
|
// By reducing the value from the driver by a tiny amount, this property should be chosen.
|
||||||
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
|
// At the same time, we'll be able to find out if the return constraints will be properly set
|
||||||
// to the best constraints.
|
// to the best constraints.
|
||||||
Width: prop.Int(expectedProp.Width - 1),
|
cases := map[string]struct {
|
||||||
Height: prop.Int(expectedProp.Width),
|
width, height int
|
||||||
FrameFormat: prop.FrameFormat(expectedProp.FrameFormat),
|
frameFormat frame.Format
|
||||||
FrameRate: prop.Float(30.0),
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,4 +169,74 @@ func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
|
|||||||
s.FrameRate != expectedProp.FrameRate {
|
s.FrameRate != expectedProp.FrameRate {
|
||||||
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
|
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("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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := selectBestDriver(filterFn, wantConstraints)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expect to not find a driver that fits the constraints")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package mediadevices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MediaStream is an interface that represents a collection of existing tracks.
|
// MediaStream is an interface that represents a collection of existing tracks.
|
||||||
@@ -23,7 +25,7 @@ type mediaStream struct {
|
|||||||
l sync.RWMutex
|
l sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackTypeDefault MediaDeviceType = 0
|
const trackTypeDefault webrtc.RTPCodecType = 0
|
||||||
|
|
||||||
// NewMediaStream creates a MediaStream interface that's defined in
|
// NewMediaStream creates a MediaStream interface that's defined in
|
||||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream
|
// https://w3c.github.io/mediacapture-main/#dom-mediastream
|
||||||
@@ -40,11 +42,11 @@ func NewMediaStream(tracks ...Track) (MediaStream, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mediaStream) GetAudioTracks() []Track {
|
func (m *mediaStream) GetAudioTracks() []Track {
|
||||||
return m.queryTracks(AudioInput)
|
return m.queryTracks(webrtc.RTPCodecTypeAudio)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mediaStream) GetVideoTracks() []Track {
|
func (m *mediaStream) GetVideoTracks() []Track {
|
||||||
return m.queryTracks(VideoInput)
|
return m.queryTracks(webrtc.RTPCodecTypeVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mediaStream) GetTracks() []Track {
|
func (m *mediaStream) GetTracks() []Track {
|
||||||
@@ -53,7 +55,7 @@ func (m *mediaStream) GetTracks() []Track {
|
|||||||
|
|
||||||
// queryTracks returns all tracks that are the same kind as t.
|
// queryTracks returns all tracks that are the same kind as t.
|
||||||
// If t is 0, which is the default, queryTracks will return all the tracks.
|
// If t is 0, which is the default, queryTracks will return all the tracks.
|
||||||
func (m *mediaStream) queryTracks(t MediaDeviceType) []Track {
|
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Track {
|
||||||
m.l.RLock()
|
m.l.RLock()
|
||||||
defer m.l.RUnlock()
|
defer m.l.RUnlock()
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockMediaStreamTrack struct {
|
type mockMediaStreamTrack struct {
|
||||||
@@ -15,26 +15,41 @@ func (track *mockMediaStreamTrack) ID() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (track *mockMediaStreamTrack) StreamID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (track *mockMediaStreamTrack) RID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) Close() error {
|
func (track *mockMediaStreamTrack) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) Kind() MediaDeviceType {
|
func (track *mockMediaStreamTrack) Kind() webrtc.RTPCodecType {
|
||||||
return track.kind
|
switch track.kind {
|
||||||
|
case AudioInput:
|
||||||
|
return webrtc.RTPCodecTypeAudio
|
||||||
|
case VideoInput:
|
||||||
|
return webrtc.RTPCodecTypeVideo
|
||||||
|
default:
|
||||||
|
panic("invalid track kind")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) OnEnded(handler func(error)) {
|
func (track *mockMediaStreamTrack) OnEnded(handler func(error)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) Bind(pc *webrtc.PeerConnection) (*webrtc.Track, error) {
|
func (track *mockMediaStreamTrack) Bind(ctx webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) {
|
||||||
return nil, nil
|
return webrtc.RTPCodecParameters{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) Unbind(pc *webrtc.PeerConnection) error {
|
func (track *mockMediaStreamTrack) Unbind(ctx webrtc.TrackLocalContext) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (track *mockMediaStreamTrack) NewRTPReader(codecName string, mtu int) (RTPReadCloser, error) {
|
func (track *mockMediaStreamTrack) NewRTPReader(codecName string, ssrc uint32, mtu int) (RTPReadCloser, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#define MAX_DEVICES 8
|
#define MAX_DEVICES 8
|
||||||
#define MAX_PROPERTIES 64
|
#define MAX_PROPERTIES 64
|
||||||
#define MAX_DEVICE_UID_CHARS 64
|
#define MAX_DEVICE_UID_CHARS 64
|
||||||
|
#define MAX_DEVICE_NAME_CHARS 64
|
||||||
|
|
||||||
typedef const char* STATUS;
|
typedef const char* STATUS;
|
||||||
static STATUS STATUS_OK = (STATUS) NULL;
|
static STATUS STATUS_OK = (STATUS) NULL;
|
||||||
@@ -45,7 +46,8 @@ typedef enum AVBindMediaType {
|
|||||||
typedef enum AVBindFrameFormat {
|
typedef enum AVBindFrameFormat {
|
||||||
AVBindFrameFormatI420,
|
AVBindFrameFormatI420,
|
||||||
AVBindFrameFormatNV21,
|
AVBindFrameFormatNV21,
|
||||||
AVBindFrameFormatYUY2,
|
AVBindFrameFormatNV12,
|
||||||
|
AVBindFrameFormatYUYV,
|
||||||
AVBindFrameFormatUYVY,
|
AVBindFrameFormatUYVY,
|
||||||
} AVBindFrameFormat;
|
} AVBindFrameFormat;
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ typedef struct AVBindSession AVBindSession, *PAVBindSession;
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char uid[MAX_DEVICE_UID_CHARS + 1];
|
char uid[MAX_DEVICE_UID_CHARS + 1];
|
||||||
|
char name[MAX_DEVICE_NAME_CHARS + 1];
|
||||||
} AVBindDevice, *PAVBindDevice;
|
} AVBindDevice, *PAVBindDevice;
|
||||||
|
|
||||||
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
||||||
|
@@ -46,6 +46,8 @@
|
|||||||
} \
|
} \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
|
static NSString *const UnrecognizedMacOSVersionException = @"UnrecognizedMacOSVersionException";
|
||||||
|
|
||||||
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||||
|
|
||||||
@property (readonly) AVBindDataCallback mCallback;
|
@property (readonly) AVBindDataCallback mCallback;
|
||||||
@@ -82,24 +84,53 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageBuffer = CVBufferRetain(imageBuffer);
|
CVBufferRetain(imageBuffer);
|
||||||
CVReturn ret =
|
CVReturn ret =
|
||||||
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
if (ret != kCVReturnSuccess) {
|
if (ret != kCVReturnSuccess) {
|
||||||
|
CVBufferRelease(imageBuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t heightY = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
|
// Handle NV12 special case
|
||||||
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
|
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
|
||||||
|
if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
|
||||||
|
// Get actual dimensions of image (without padding)
|
||||||
|
size_t width = CVPixelBufferGetWidth(imageBuffer);
|
||||||
|
size_t height = CVPixelBufferGetHeight(imageBuffer);
|
||||||
|
size_t totalSize = /*Y plane*/ width * height + /*UV plane*/ width * height / 2;
|
||||||
|
|
||||||
size_t heightUV = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
|
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
|
||||||
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
|
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
|
||||||
|
|
||||||
int len = (int)((heightY * bytesPerRowY) + (2 * heightUV * bytesPerRowUV));
|
void *mergedBuffer = malloc(totalSize);
|
||||||
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
|
if (!mergedBuffer) {
|
||||||
_mCallback(_mPUserData, buf, len);
|
NSLog(@"Failed to allocate memory for merged buffer");
|
||||||
|
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
CVBufferRelease(imageBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
|
// Truncate data where we know it should end to strip padding
|
||||||
|
void *yPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
|
||||||
|
for (size_t row = 0; row < height; ++row) {
|
||||||
|
memcpy(mergedBuffer + row * width, yPlaneBuf + row * bytesPerRowY, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *uvPlaneBuf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
|
||||||
|
for (size_t row = 0; row < height / 2; ++row) {
|
||||||
|
memcpy(mergedBuffer + width * height + row * width, uvPlaneBuf + row * bytesPerRowUV, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mCallback(_mPUserData, mergedBuffer, (int)totalSize);
|
||||||
|
free(mergedBuffer);
|
||||||
|
} else {
|
||||||
|
void *buf = CVPixelBufferGetBaseAddress(imageBuffer);
|
||||||
|
size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
|
||||||
|
_mCallback(_mPUserData, buf, (int)dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
CVBufferRelease(imageBuffer);
|
CVBufferRelease(imageBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +164,21 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
|||||||
|
|
||||||
STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
||||||
STATUS retStatus = STATUS_OK;
|
STATUS retStatus = STATUS_OK;
|
||||||
|
// Useful mapping reference from ffmpeg:
|
||||||
|
// https://github.com/FFmpeg/FFmpeg/blob/c810a9502cebe32e1dd08ee3d0d17053dde44aa9/libavdevice/avfoundation.m#L53-L80
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case AVBindFrameFormatNV21:
|
case AVBindFrameFormatI420:
|
||||||
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
|
*pFourCC = kCVPixelFormatType_420YpCbCr8Planar;
|
||||||
|
break;
|
||||||
|
case AVBindFrameFormatNV12:
|
||||||
|
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||||
break;
|
break;
|
||||||
case AVBindFrameFormatUYVY:
|
case AVBindFrameFormatUYVY:
|
||||||
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
||||||
break;
|
break;
|
||||||
|
case AVBindFrameFormatYUYV:
|
||||||
|
*pFourCC = kCVPixelFormatType_422YpCbCr8_yuvs;
|
||||||
|
break;
|
||||||
// TODO: Add the rest of frame formats
|
// TODO: Add the rest of frame formats
|
||||||
default:
|
default:
|
||||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||||
@@ -150,12 +189,19 @@ STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
|||||||
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
||||||
STATUS retStatus = STATUS_OK;
|
STATUS retStatus = STATUS_OK;
|
||||||
switch (fourCC) {
|
switch (fourCC) {
|
||||||
|
case kCVPixelFormatType_420YpCbCr8Planar:
|
||||||
|
*pFormat = AVBindFrameFormatI420;
|
||||||
|
break;
|
||||||
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
||||||
*pFormat = AVBindFrameFormatNV21;
|
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
||||||
|
*pFormat = AVBindFrameFormatNV12;
|
||||||
break;
|
break;
|
||||||
case kCVPixelFormatType_422YpCbCr8:
|
case kCVPixelFormatType_422YpCbCr8:
|
||||||
*pFormat = AVBindFrameFormatUYVY;
|
*pFormat = AVBindFrameFormatUYVY;
|
||||||
break;
|
break;
|
||||||
|
case kCVPixelFormatType_422YpCbCr8_yuvs:
|
||||||
|
*pFormat = AVBindFrameFormatYUYV;
|
||||||
|
break;
|
||||||
// TODO: Add the rest of frame formats
|
// TODO: Add the rest of frame formats
|
||||||
default:
|
default:
|
||||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||||
@@ -173,11 +219,28 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
|
|||||||
|
|
||||||
PAVBindDevice pDevice;
|
PAVBindDevice pDevice;
|
||||||
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
|
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
|
||||||
NSArray *refAllTypes = @[
|
|
||||||
|
NSArray *refAllTypes;
|
||||||
|
#if defined(MAC_OS_VERSION_14_0)
|
||||||
|
if (@available(macOS 14.0, *)) {
|
||||||
|
refAllTypes = @[
|
||||||
|
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||||
|
AVCaptureDeviceTypeMicrophone,
|
||||||
|
AVCaptureDeviceTypeExternal,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
@throw [NSException exceptionWithName:UnrecognizedMacOSVersionException
|
||||||
|
reason:@"Unrecognized or unsupported macOS version detected."
|
||||||
|
userInfo:nil];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
refAllTypes = @[
|
||||||
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||||
AVCaptureDeviceTypeBuiltInMicrophone,
|
AVCaptureDeviceTypeBuiltInMicrophone,
|
||||||
AVCaptureDeviceTypeExternalUnknown
|
AVCaptureDeviceTypeExternalUnknown,
|
||||||
];
|
];
|
||||||
|
#endif
|
||||||
|
|
||||||
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
|
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
|
||||||
discoverySessionWithDeviceTypes: refAllTypes
|
discoverySessionWithDeviceTypes: refAllTypes
|
||||||
mediaType: _mediaType
|
mediaType: _mediaType
|
||||||
@@ -192,6 +255,8 @@ STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *p
|
|||||||
pDevice = devices + i;
|
pDevice = devices + i;
|
||||||
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
||||||
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
||||||
|
strncpy(pDevice->name, refDevice.localizedName.UTF8String, MAX_DEVICE_NAME_CHARS);
|
||||||
|
pDevice->name[MAX_DEVICE_NAME_CHARS] = '\0';
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +367,16 @@ cleanup:
|
|||||||
return retStatus;
|
return retStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NSString* FourCCString(FourCharCode code) {
|
||||||
|
NSString *result = [NSString stringWithFormat:@"%c%c%c%c",
|
||||||
|
(code >> 24) & 0xff,
|
||||||
|
(code >> 16) & 0xff,
|
||||||
|
(code >> 8) & 0xff,
|
||||||
|
code & 0xff];
|
||||||
|
NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet];
|
||||||
|
return [result stringByTrimmingCharactersInSet:characterSet];
|
||||||
|
}
|
||||||
|
|
||||||
STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *ppProperties, int *pLen) {
|
STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *ppProperties, int *pLen) {
|
||||||
STATUS retStatus = STATUS_OK;
|
STATUS retStatus = STATUS_OK;
|
||||||
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
|
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
|
||||||
@@ -319,12 +394,14 @@ STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *pp
|
|||||||
for (AVCaptureDeviceFormat *refFormat in refDevice.formats) {
|
for (AVCaptureDeviceFormat *refFormat in refDevice.formats) {
|
||||||
// TODO: Probably gives a warn to the user
|
// TODO: Probably gives a warn to the user
|
||||||
if (len >= MAX_PROPERTIES) {
|
if (len >= MAX_PROPERTIES) {
|
||||||
|
NSLog(@"[WARNING] skipping the rest of properties due to MAX_PROPERTIES");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([refFormat.mediaType isEqual:AVMediaTypeVideo]) {
|
if ([refFormat.mediaType isEqual:AVMediaTypeVideo]) {
|
||||||
fourCC = CMFormatDescriptionGetMediaSubType(refFormat.formatDescription);
|
fourCC = CMFormatDescriptionGetMediaSubType(refFormat.formatDescription);
|
||||||
if (frameFormatFromFourCC(fourCC, &pProperty->frameFormat) != STATUS_OK) {
|
if (frameFormatFromFourCC(fourCC, &pProperty->frameFormat) != STATUS_OK) {
|
||||||
|
NSLog(@"[WARNING] skipping %@ %dx%d since it's not supported", FourCCString(fourCC), videoDimensions.width, videoDimensions.height);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package avfoundation
|
package avfoundation
|
||||||
|
|
||||||
// extern void onData(void*, void*, int);
|
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,11 +17,10 @@ type handleID int
|
|||||||
|
|
||||||
//export onData
|
//export onData
|
||||||
func onData(userData unsafe.Pointer, buf unsafe.Pointer, length C.int) {
|
func onData(userData unsafe.Pointer, buf unsafe.Pointer, length C.int) {
|
||||||
data := C.GoBytes(buf, length)
|
|
||||||
|
|
||||||
handleNum := (*C.int)(userData)
|
handleNum := (*C.int)(userData)
|
||||||
cb, ok := lookup(handleID(*handleNum))
|
cb, ok := lookup(handleID(*handleNum))
|
||||||
if ok {
|
if ok {
|
||||||
|
data := C.GoBytes(buf, length)
|
||||||
cb(data)
|
cb(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,10 @@ package avfoundation
|
|||||||
// }
|
// }
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
@@ -32,6 +34,7 @@ type Device struct {
|
|||||||
// UID is a unique identifier for a device
|
// UID is a unique identifier for a device
|
||||||
UID string
|
UID string
|
||||||
cDevice C.AVBindDevice
|
cDevice C.AVBindDevice
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
||||||
@@ -40,8 +43,10 @@ func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
|||||||
return C.AVBindFrameFormatI420, true
|
return C.AVBindFrameFormatI420, true
|
||||||
case frame.FormatNV21:
|
case frame.FormatNV21:
|
||||||
return C.AVBindFrameFormatNV21, true
|
return C.AVBindFrameFormatNV21, true
|
||||||
case frame.FormatYUY2:
|
case frame.FormatNV12:
|
||||||
return C.AVBindFrameFormatYUY2, true
|
return C.AVBindFrameFormatNV12, true
|
||||||
|
case frame.FormatYUYV:
|
||||||
|
return C.AVBindFrameFormatYUYV, true
|
||||||
case frame.FormatUYVY:
|
case frame.FormatUYVY:
|
||||||
return C.AVBindFrameFormatUYVY, true
|
return C.AVBindFrameFormatUYVY, true
|
||||||
default:
|
default:
|
||||||
@@ -55,8 +60,10 @@ func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
|
|||||||
return frame.FormatI420, true
|
return frame.FormatI420, true
|
||||||
case C.AVBindFrameFormatNV21:
|
case C.AVBindFrameFormatNV21:
|
||||||
return frame.FormatNV21, true
|
return frame.FormatNV21, true
|
||||||
case C.AVBindFrameFormatYUY2:
|
case C.AVBindFrameFormatNV12:
|
||||||
return frame.FormatYUY2, true
|
return frame.FormatNV12, true
|
||||||
|
case C.AVBindFrameFormatYUYV:
|
||||||
|
return frame.FormatYUYV, true
|
||||||
case C.AVBindFrameFormatUYVY:
|
case C.AVBindFrameFormatUYVY:
|
||||||
return frame.FormatUYVY, true
|
return frame.FormatUYVY, true
|
||||||
default:
|
default:
|
||||||
@@ -81,6 +88,7 @@ func Devices(mediaType MediaType) ([]Device, error) {
|
|||||||
for i := range devices {
|
for i := range devices {
|
||||||
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
||||||
devices[i].cDevice = cDevices[i]
|
devices[i].cDevice = cDevices[i]
|
||||||
|
devices[i].Name = C.GoString(&cDevices[i].name[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices, nil
|
return devices, nil
|
||||||
@@ -92,6 +100,10 @@ type ReadCloser struct {
|
|||||||
dataChan chan []byte
|
dataChan chan []byte
|
||||||
id handleID
|
id handleID
|
||||||
onClose func()
|
onClose func()
|
||||||
|
cancelCtx context.Context
|
||||||
|
cancelFunc func()
|
||||||
|
closeWG sync.WaitGroup
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReadCloser(onClose func()) *ReadCloser {
|
func newReadCloser(onClose func()) *ReadCloser {
|
||||||
@@ -99,12 +111,25 @@ func newReadCloser(onClose func()) *ReadCloser {
|
|||||||
rc.dataChan = make(chan []byte, 1)
|
rc.dataChan = make(chan []byte, 1)
|
||||||
rc.onClose = onClose
|
rc.onClose = onClose
|
||||||
rc.id = register(rc.dataCb)
|
rc.id = register(rc.dataCb)
|
||||||
|
cancelCtx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
rc.cancelCtx = cancelCtx
|
||||||
|
rc.cancelFunc = cancelFunc
|
||||||
return &rc
|
return &rc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *ReadCloser) dataCb(data []byte) {
|
func (rc *ReadCloser) dataCb(data []byte) {
|
||||||
|
rc.closeWG.Add(1)
|
||||||
|
defer rc.closeWG.Done()
|
||||||
|
|
||||||
// TODO: add a policy for slow reader
|
// TODO: add a policy for slow reader
|
||||||
rc.dataChan <- data
|
if rc.cancelCtx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
// Use the Done channel to avoid waiting for new data from closed camera
|
||||||
|
case <-rc.cancelCtx.Done():
|
||||||
|
case rc.dataChan <- data:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads raw data, the format is determined by the media type and property:
|
// Read reads raw data, the format is determined by the media type and property:
|
||||||
@@ -120,17 +145,28 @@ func (rc *ReadCloser) Read() ([]byte, func(), error) {
|
|||||||
|
|
||||||
// Close closes the capturing session, and no data will flow anymore
|
// Close closes the capturing session, and no data will flow anymore
|
||||||
func (rc *ReadCloser) Close() {
|
func (rc *ReadCloser) Close() {
|
||||||
|
rc.lock.Lock()
|
||||||
|
defer rc.lock.Unlock()
|
||||||
|
|
||||||
|
if rc.cancelCtx.Err() != nil {
|
||||||
|
return // already closed
|
||||||
|
}
|
||||||
|
|
||||||
if rc.onClose != nil {
|
if rc.onClose != nil {
|
||||||
rc.onClose()
|
rc.onClose()
|
||||||
}
|
}
|
||||||
close(rc.dataChan)
|
rc.cancelFunc()
|
||||||
unregister(rc.id)
|
unregister(rc.id)
|
||||||
|
rc.closeWG.Wait()
|
||||||
|
close(rc.dataChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session represents a capturing session.
|
// Session represents a capturing session.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
device Device
|
device Device
|
||||||
cSession C.PAVBindSession
|
cSession C.PAVBindSession
|
||||||
|
lock sync.Mutex
|
||||||
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSession creates a new capturing session
|
// NewSession creates a new capturing session
|
||||||
@@ -148,6 +184,13 @@ func NewSession(device Device) (*Session, error) {
|
|||||||
|
|
||||||
// Close stops capturing session and frees up resources
|
// Close stops capturing session and frees up resources
|
||||||
func (session *Session) Close() error {
|
func (session *Session) Close() error {
|
||||||
|
session.lock.Lock()
|
||||||
|
defer session.lock.Unlock()
|
||||||
|
if session.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
session.closed = true
|
||||||
|
|
||||||
if session.cSession == nil {
|
if session.cSession == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -1,35 +1,91 @@
|
|||||||
package codec
|
package codec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/rtp"
|
||||||
|
"github.com/pion/rtp/codecs"
|
||||||
|
"github.com/pion/webrtc/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RTPCodec wraps webrtc.RTPCodec. RTPCodec might extend webrtc.RTPCodec in the future.
|
// RTPCodec wraps webrtc.RTPCodec. RTPCodec might extend webrtc.RTPCodec in the future.
|
||||||
type RTPCodec struct {
|
type RTPCodec struct {
|
||||||
*webrtc.RTPCodec
|
webrtc.RTPCodecParameters
|
||||||
|
rtp.Payloader
|
||||||
|
|
||||||
|
// Latency of static frame size codec.
|
||||||
|
Latency time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPH264Codec is a helper to create an H264 codec
|
// NewRTPH264Codec is a helper to create an H264 codec
|
||||||
func NewRTPH264Codec(clockrate uint32) *RTPCodec {
|
func NewRTPH264Codec(clockrate uint32) *RTPCodec {
|
||||||
return &RTPCodec{webrtc.NewRTPH264Codec(webrtc.DefaultPayloadTypeH264, clockrate)}
|
return &RTPCodec{
|
||||||
|
RTPCodecParameters: webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeH264,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
|
||||||
|
RTCPFeedback: nil,
|
||||||
|
},
|
||||||
|
PayloadType: 125,
|
||||||
|
},
|
||||||
|
Payloader: &codecs.H264Payloader{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPVP8Codec is a helper to create an VP8 codec
|
// NewRTPVP8Codec is a helper to create an VP8 codec
|
||||||
func NewRTPVP8Codec(clockrate uint32) *RTPCodec {
|
func NewRTPVP8Codec(clockrate uint32) *RTPCodec {
|
||||||
return &RTPCodec{webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, clockrate)}
|
return &RTPCodec{
|
||||||
|
RTPCodecParameters: webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeVP8,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: nil,
|
||||||
|
},
|
||||||
|
PayloadType: 96,
|
||||||
|
},
|
||||||
|
Payloader: &codecs.VP8Payloader{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPVP9Codec is a helper to create an VP9 codec
|
// NewRTPVP9Codec is a helper to create an VP9 codec
|
||||||
func NewRTPVP9Codec(clockrate uint32) *RTPCodec {
|
func NewRTPVP9Codec(clockrate uint32) *RTPCodec {
|
||||||
return &RTPCodec{webrtc.NewRTPVP9Codec(webrtc.DefaultPayloadTypeVP9, clockrate)}
|
return &RTPCodec{
|
||||||
|
RTPCodecParameters: webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeVP9,
|
||||||
|
ClockRate: 90000,
|
||||||
|
Channels: 0,
|
||||||
|
SDPFmtpLine: "",
|
||||||
|
RTCPFeedback: nil,
|
||||||
|
},
|
||||||
|
PayloadType: 98,
|
||||||
|
},
|
||||||
|
Payloader: &codecs.VP9Payloader{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPOpusCodec is a helper to create an Opus codec
|
// NewRTPOpusCodec is a helper to create an Opus codec
|
||||||
func NewRTPOpusCodec(clockrate uint32) *RTPCodec {
|
func NewRTPOpusCodec(clockrate uint32) *RTPCodec {
|
||||||
return &RTPCodec{webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, clockrate)}
|
return &RTPCodec{
|
||||||
|
RTPCodecParameters: webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||||
|
MimeType: webrtc.MimeTypeOpus,
|
||||||
|
ClockRate: 48000,
|
||||||
|
Channels: 2,
|
||||||
|
SDPFmtpLine: "minptime=10;useinbandfec=1",
|
||||||
|
RTCPFeedback: nil,
|
||||||
|
},
|
||||||
|
PayloadType: 111,
|
||||||
|
},
|
||||||
|
Payloader: &codecs.OpusPayloader{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioEncoderBuilder is the interface that wraps basic operations that are
|
// AudioEncoderBuilder is the interface that wraps basic operations that are
|
||||||
@@ -56,15 +112,37 @@ type VideoEncoderBuilder interface {
|
|||||||
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
|
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 {
|
type ReadCloser interface {
|
||||||
Read() (b []byte, release func(), err error)
|
Read() (b []byte, release func(), err error)
|
||||||
Close() 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
|
// SetBitRate sets current target bitrate, lower bitrate means smaller data will be transmitted
|
||||||
// but this also means that the quality will also be lower.
|
// but this also means that the quality will also be lower.
|
||||||
SetBitRate(int) error
|
SetBitRate(int) error
|
||||||
// ForceKeyFrame forces the next frame to be a keyframe, aka intra-frame.
|
|
||||||
ForceKeyFrame() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseParams represents an codec's encoding properties
|
// BaseParams represents an codec's encoding properties
|
||||||
|
159
pkg/codec/internal/codectest/codectest.go
Normal file
159
pkg/codec/internal/codectest/codectest.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AudioEncoderReadAfterCloseTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
|
||||||
|
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
|
||||||
|
return w, nil, nil
|
||||||
|
}), p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := assertNoPanic(t, func() error {
|
||||||
|
_, _, err := enc.Read()
|
||||||
|
return err
|
||||||
|
}, "on Read()"); err != io.EOF {
|
||||||
|
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VideoEncoderReadAfterCloseTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
|
||||||
|
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
return img, nil, nil
|
||||||
|
}), p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := assertNoPanic(t, enc.Close, "on Close()"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := assertNoPanic(t, func() error {
|
||||||
|
_, _, err := enc.Read()
|
||||||
|
return err
|
||||||
|
}, "on Read()"); err != io.EOF {
|
||||||
|
t.Fatalf("Expected: %v, got: %v", io.EOF, err)
|
||||||
|
}
|
||||||
|
}
|
@@ -64,10 +64,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
imgReal := img.(*image.YCbCr)
|
imgReal := img.(*image.YCbCr)
|
||||||
var y, cb, cr C.Slice
|
var y, cb, cr C.Slice
|
||||||
y.data = (*C.uchar)(&imgReal.Y[0])
|
y.data = (*C.uchar)(&imgReal.Y[0])
|
||||||
@@ -91,12 +92,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, err
|
return encoded, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) SetBitRate(b int) error {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
panic("SetBitRate is not implemented")
|
return e
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoder) ForceKeyFrame() error {
|
|
||||||
panic("ForceKeyFrame is not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) Close() error {
|
func (e *encoder) Close() error {
|
||||||
|
84
pkg/codec/mmal/mmal_test.go
Normal file
84
pkg/codec/mmal/mmal_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := NewParams()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
codectest.VideoEncoderReadAfterCloseTest(t, &p,
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
47
pkg/codec/openh264/Makefile
Normal file
47
pkg/codec/openh264/Makefile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
git_url := https://github.com/cisco/openh264.git
|
||||||
|
version := v2.1.1
|
||||||
|
src_root_dir := src
|
||||||
|
lib_dir := lib
|
||||||
|
include_dir := include/openh264
|
||||||
|
lib_prefix := libopenh264
|
||||||
|
src_dir := $(src_root_dir)/$(MEDIADEVICES_TARGET_PLATFORM)
|
||||||
|
output_path := $(lib_dir)/$(lib_prefix)-$(MEDIADEVICES_TARGET_PLATFORM).a
|
||||||
|
|
||||||
|
# OS and Arch mapping to OpenH264 parameters
|
||||||
|
ifeq (windows,$(MEDIADEVICES_TARGET_OS))
|
||||||
|
os := mingw_nt
|
||||||
|
else
|
||||||
|
os := $(MEDIADEVICES_TARGET_OS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(findstring $(MEDIADEVICES_TARGET_ARCH),armv6 armv7 armv8))
|
||||||
|
arch := arm
|
||||||
|
else ifeq (x64,$(MEDIADEVICES_TARGET_ARCH))
|
||||||
|
arch := x86_64
|
||||||
|
else
|
||||||
|
arch := $(MEDIADEVICES_TARGET_ARCH)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: guard-MEDIADEVICES_TARGET_PLATFORM guard-MEDIADEVICES_TARGET_PLATFORM \
|
||||||
|
guard-MEDIADEVICES_TARGET_OS guard-MEDIADEVICES_TARGET_ARCH \
|
||||||
|
$(output_path) headers
|
||||||
|
|
||||||
|
headers: | $(src_dir) $(include_dir)
|
||||||
|
@cp $(src_dir)/codec/api/svc/*.h $(include_dir)
|
||||||
|
|
||||||
|
$(output_path): $(src_dir)/$(lib_prefix).a | $(lib_dir)
|
||||||
|
@cp $< $@
|
||||||
|
|
||||||
|
$(src_dir)/$(lib_prefix).a: | $(src_dir)
|
||||||
|
$(MEDIADEVICES_TOOLCHAIN_BIN) make --directory=$(src_dir) $(lib_prefix).a \
|
||||||
|
OS=$(os) ARCH=$(arch)
|
||||||
|
|
||||||
|
$(src_dir): | $(src_root_dir)
|
||||||
|
git clone --depth=1 --branch=$(version) $(git_url) $@
|
||||||
|
|
||||||
|
$(src_root_dir) $(lib_dir) $(include_dir):
|
||||||
|
@mkdir -p $@
|
||||||
|
|
||||||
|
guard-%:
|
||||||
|
@if [ -z ${$*} ]; then echo "$* is a required environment variable"; exit 1; fi
|
@@ -21,29 +21,26 @@ Encoder *enc_new(const EncoderOptions opts, int *eresult) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove hardcoded values
|
params.iUsageType = opts.usage_type;
|
||||||
params.iUsageType = CAMERA_VIDEO_REAL_TIME;
|
|
||||||
params.iPicWidth = opts.width;
|
params.iPicWidth = opts.width;
|
||||||
params.iPicHeight = opts.height;
|
params.iPicHeight = opts.height;
|
||||||
params.iTargetBitrate = opts.target_bitrate;
|
params.iTargetBitrate = opts.target_bitrate;
|
||||||
params.iMaxBitrate = opts.target_bitrate;
|
params.iMaxBitrate = opts.target_bitrate;
|
||||||
params.iRCMode = RC_BITRATE_MODE;
|
params.iRCMode = opts.rc_mode;
|
||||||
params.fMaxFrameRate = opts.max_fps;
|
params.fMaxFrameRate = opts.max_fps;
|
||||||
params.bEnableFrameSkip = true;
|
params.bEnableFrameSkip = opts.enable_frame_skip;
|
||||||
params.uiMaxNalSize = 0;
|
params.uiMaxNalSize = opts.max_nal_size;
|
||||||
params.uiIntraPeriod = 30;
|
params.uiIntraPeriod = opts.intra_period;
|
||||||
// set to 0, so that it'll automatically use multi threads when needed
|
params.iMultipleThreadIdc = opts.multiple_thread_idc;
|
||||||
params.iMultipleThreadIdc = 0;
|
|
||||||
// The base spatial layer 0 is the only one we use.
|
// The base spatial layer 0 is the only one we use.
|
||||||
params.sSpatialLayers[0].iVideoWidth = params.iPicWidth;
|
params.sSpatialLayers[0].iVideoWidth = params.iPicWidth;
|
||||||
params.sSpatialLayers[0].iVideoHeight = params.iPicHeight;
|
params.sSpatialLayers[0].iVideoHeight = params.iPicHeight;
|
||||||
params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate;
|
params.sSpatialLayers[0].fFrameRate = params.fMaxFrameRate;
|
||||||
params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate;
|
params.sSpatialLayers[0].iSpatialBitrate = params.iTargetBitrate;
|
||||||
params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate;
|
params.sSpatialLayers[0].iMaxSpatialBitrate = params.iTargetBitrate;
|
||||||
// Single NAL unit mode
|
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = opts.slice_num;
|
||||||
params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
|
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = opts.slice_mode;
|
||||||
params.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SIZELIMITED_SLICE;
|
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = opts.slice_size_constraint;
|
||||||
params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint = 12800;
|
|
||||||
|
|
||||||
rv = engine->InitializeExt(¶ms);
|
rv = engine->InitializeExt(¶ms);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
@@ -80,12 +77,16 @@ Slice enc_encode(Encoder *e, Frame f, int *eresult) {
|
|||||||
SFrameBSInfo info = {0};
|
SFrameBSInfo info = {0};
|
||||||
Slice payload = {0};
|
Slice payload = {0};
|
||||||
|
|
||||||
|
if(e->force_key_frame == 1) {
|
||||||
|
e->engine->ForceIntraFrame(true);
|
||||||
|
e->force_key_frame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
pic.iPicWidth = f.width;
|
pic.iPicWidth = f.width;
|
||||||
pic.iPicHeight = f.height;
|
pic.iPicHeight = f.height;
|
||||||
pic.iColorFormat = videoFormatI420;
|
pic.iColorFormat = videoFormatI420;
|
||||||
// We always received I420 format
|
pic.iStride[0] = f.ystride;
|
||||||
pic.iStride[0] = pic.iPicWidth;
|
pic.iStride[1] = pic.iStride[2] = f.cstride;
|
||||||
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth / 2;
|
|
||||||
pic.pData[0] = (unsigned char *)f.y;
|
pic.pData[0] = (unsigned char *)f.y;
|
||||||
pic.pData[1] = (unsigned char *)f.u;
|
pic.pData[1] = (unsigned char *)f.u;
|
||||||
pic.pData[2] = (unsigned char *)f.v;
|
pic.pData[2] = (unsigned char *)f.v;
|
||||||
|
@@ -12,6 +12,8 @@ typedef struct Slice {
|
|||||||
|
|
||||||
typedef struct Frame {
|
typedef struct Frame {
|
||||||
void *y, *u, *v;
|
void *y, *u, *v;
|
||||||
|
int ystride;
|
||||||
|
int cstride;
|
||||||
int height;
|
int height;
|
||||||
int width;
|
int width;
|
||||||
} Frame;
|
} Frame;
|
||||||
@@ -20,6 +22,15 @@ typedef struct EncoderOptions {
|
|||||||
int width, height;
|
int width, height;
|
||||||
int target_bitrate;
|
int target_bitrate;
|
||||||
float max_fps;
|
float max_fps;
|
||||||
|
EUsageType usage_type;
|
||||||
|
RC_MODES rc_mode;
|
||||||
|
bool enable_frame_skip;
|
||||||
|
unsigned int max_nal_size;
|
||||||
|
unsigned int intra_period;
|
||||||
|
int multiple_thread_idc;
|
||||||
|
unsigned int slice_num;
|
||||||
|
SliceModeEnum slice_mode;
|
||||||
|
unsigned int slice_size_constraint;
|
||||||
} EncoderOptions;
|
} EncoderOptions;
|
||||||
|
|
||||||
typedef struct Encoder {
|
typedef struct Encoder {
|
||||||
@@ -27,6 +38,7 @@ typedef struct Encoder {
|
|||||||
ISVCEncoder *engine;
|
ISVCEncoder *engine;
|
||||||
unsigned char *buff;
|
unsigned char *buff;
|
||||||
int buff_size;
|
int buff_size;
|
||||||
|
int force_key_frame;
|
||||||
} Encoder;
|
} Encoder;
|
||||||
|
|
||||||
Encoder *enc_new(const EncoderOptions params, int *eresult);
|
Encoder *enc_new(const EncoderOptions params, int *eresult);
|
||||||
|
@@ -1,36 +0,0 @@
|
|||||||
GIT_URL=https://github.com/cisco/openh264.git
|
|
||||||
VERSION=v2.1.1
|
|
||||||
SRC_DIR=src
|
|
||||||
LIB_DIR=lib
|
|
||||||
INCLUDE_DIR=include/openh264
|
|
||||||
ROOT_DIR=${PWD}
|
|
||||||
LIB_PREFIX=libopenh264
|
|
||||||
|
|
||||||
OS=${MEDIADEVICES_TARGET_OS}
|
|
||||||
ARCH=${MEDIADEVICES_TARGET_ARCH}
|
|
||||||
|
|
||||||
case ${MEDIADEVICES_TARGET_OS} in
|
|
||||||
windows)
|
|
||||||
OS=mingw_nt
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case ${MEDIADEVICES_TARGET_ARCH} in
|
|
||||||
armv6 | armv7 | armv8)
|
|
||||||
ARCH=arm
|
|
||||||
;;
|
|
||||||
x64)
|
|
||||||
ARCH=x86_64
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
mkdir -p ${LIB_DIR} ${INCLUDE_DIR}
|
|
||||||
|
|
||||||
git clone --depth=1 --branch=${VERSION} ${GIT_URL} ${SRC_DIR}
|
|
||||||
cd ${SRC_DIR}
|
|
||||||
${MEDIADEVICES_TOOLCHAIN_BIN} make -j2 ${LIB_PREFIX}.a OS=${OS} ARCH=${ARCH}
|
|
||||||
${MEDIADEVICES_TOOLCHAIN_BIN} echo $PATH
|
|
||||||
mv ${LIB_PREFIX}.a ${ROOT_DIR}/${LIB_DIR}/${LIB_PREFIX}_${MEDIADEVICES_TARGET_PLATFORM}.a
|
|
||||||
mkdir -p ${ROOT_DIR}/${INCLUDE_DIR}
|
|
||||||
cp codec/api/svc/*.h ${ROOT_DIR}/${INCLUDE_DIR}
|
|
||||||
make clean
|
|
@@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
#include "codec_app_def.h"
|
#include "codec_app_def.h"
|
||||||
|
|
||||||
static const OpenH264Version g_stCodecVersion = {2, 1, 0, 2002};
|
static const OpenH264Version g_stCodecVersion = {2, 1, 1, 2005};
|
||||||
static const char* const g_strCodecVer = "OpenH264 version:2.1.0.2002";
|
static const char* const g_strCodecVer = "OpenH264 version:2.1.1.2005";
|
||||||
|
|
||||||
#define OPENH264_MAJOR (2)
|
#define OPENH264_MAJOR (2)
|
||||||
#define OPENH264_MINOR (1)
|
#define OPENH264_MINOR (1)
|
||||||
#define OPENH264_REVISION (0)
|
#define OPENH264_REVISION (1)
|
||||||
#define OPENH264_RESERVED (2002)
|
#define OPENH264_RESERVED (2005)
|
||||||
|
|
||||||
#endif // CODEC_VER_H
|
#endif // CODEC_VER_H
|
||||||
|
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -37,6 +37,15 @@ func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
|||||||
height: C.int(p.Height),
|
height: C.int(p.Height),
|
||||||
target_bitrate: C.int(params.BitRate),
|
target_bitrate: C.int(params.BitRate),
|
||||||
max_fps: C.float(p.FrameRate),
|
max_fps: C.float(p.FrameRate),
|
||||||
|
usage_type: C.EUsageType(params.UsageType),
|
||||||
|
rc_mode: C.RC_MODES(params.RCMode),
|
||||||
|
enable_frame_skip: C.bool(params.EnableFrameSkip),
|
||||||
|
max_nal_size: C.uint(params.MaxNalSize),
|
||||||
|
intra_period: C.uint(params.IntraPeriod),
|
||||||
|
multiple_thread_idc: C.int(params.MultipleThreadIdc),
|
||||||
|
slice_num: C.uint(params.SliceNum),
|
||||||
|
slice_mode: C.SliceModeEnum(params.SliceMode),
|
||||||
|
slice_size_constraint: C.uint(params.SliceSizeConstraint),
|
||||||
}, &rv)
|
}, &rv)
|
||||||
if err := errResult(rv); err != nil {
|
if err := errResult(rv); err != nil {
|
||||||
return nil, fmt.Errorf("failed in creating encoder: %v", err)
|
return nil, fmt.Errorf("failed in creating encoder: %v", err)
|
||||||
@@ -56,10 +65,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
|
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
bounds := yuvImg.Bounds()
|
bounds := yuvImg.Bounds()
|
||||||
@@ -68,6 +78,8 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
y: unsafe.Pointer(&yuvImg.Y[0]),
|
y: unsafe.Pointer(&yuvImg.Y[0]),
|
||||||
u: unsafe.Pointer(&yuvImg.Cb[0]),
|
u: unsafe.Pointer(&yuvImg.Cb[0]),
|
||||||
v: unsafe.Pointer(&yuvImg.Cr[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),
|
height: C.int(bounds.Max.Y - bounds.Min.Y),
|
||||||
width: C.int(bounds.Max.X - bounds.Min.X),
|
width: C.int(bounds.Max.X - bounds.Min.X),
|
||||||
}, &rv)
|
}, &rv)
|
||||||
@@ -79,18 +91,23 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, nil
|
return encoded, func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) SetBitRate(b int) error {
|
func (e *encoder) ForceKeyFrame() error {
|
||||||
panic("SetBitRate is not implemented")
|
e.engine.force_key_frame = C.int(1)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) ForceKeyFrame() error {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
panic("ForceKeyFrame is not implemented")
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) Close() error {
|
func (e *encoder) Close() error {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if e.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
e.closed = true
|
e.closed = true
|
||||||
|
|
||||||
var rv C.int
|
var rv C.int
|
||||||
|
@@ -4,9 +4,10 @@ package openh264
|
|||||||
|
|
||||||
//#cgo CFLAGS: -I${SRCDIR}/include
|
//#cgo CFLAGS: -I${SRCDIR}/include
|
||||||
//#cgo CXXFLAGS: -I${SRCDIR}/include
|
//#cgo CXXFLAGS: -I${SRCDIR}/include
|
||||||
//#cgo linux,arm LDFLAGS: ${SRCDIR}/lib/libopenh264_linux_armv7.a
|
//#cgo linux,arm LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-armv7.a
|
||||||
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264_linux_arm64.a
|
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-arm64.a
|
||||||
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264_linux_x64.a
|
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-linux-x64.a
|
||||||
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264_darwin_x64.a
|
//#cgo darwin,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-x64.a
|
||||||
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264_windows_x64.a -lssp
|
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopenh264-darwin-arm64.a
|
||||||
|
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopenh264-windows-x64.a -lssp
|
||||||
import "C"
|
import "C"
|
||||||
|
82
pkg/codec/openh264/openh264_test.go
Normal file
82
pkg/codec/openh264/openh264_test.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := NewParams()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
codectest.VideoEncoderReadAfterCloseTest(t, &p,
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
@@ -1,5 +1,8 @@
|
|||||||
package openh264
|
package openh264
|
||||||
|
|
||||||
|
// #include <openh264/codec_api.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
@@ -9,14 +12,62 @@ import (
|
|||||||
// Params stores libopenh264 specific encoding parameters.
|
// Params stores libopenh264 specific encoding parameters.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
codec.BaseParams
|
codec.BaseParams
|
||||||
|
UsageType UsageTypeEnum
|
||||||
|
RCMode RCModeEnum
|
||||||
|
EnableFrameSkip bool
|
||||||
|
MaxNalSize uint
|
||||||
|
IntraPeriod uint
|
||||||
|
MultipleThreadIdc int
|
||||||
|
SliceNum uint
|
||||||
|
SliceMode SliceModeEnum
|
||||||
|
SliceSizeConstraint uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UsageTypeEnum int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CameraVideoRealTime UsageTypeEnum = C.CAMERA_VIDEO_REAL_TIME ///< camera video for real-time communication
|
||||||
|
ScreenContentRealTime UsageTypeEnum = C.SCREEN_CONTENT_REAL_TIME ///< screen content signal
|
||||||
|
CameraVideoNonRealTime UsageTypeEnum = C.CAMERA_VIDEO_NON_REAL_TIME
|
||||||
|
ScreenContentNonRealTime UsageTypeEnum = C.SCREEN_CONTENT_NON_REAL_TIME
|
||||||
|
InputContentTypeAll UsageTypeEnum = C.INPUT_CONTENT_TYPE_ALL
|
||||||
|
)
|
||||||
|
|
||||||
|
type RCModeEnum int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RCQualityMode RCModeEnum = C.RC_QUALITY_MODE ///< quality mode
|
||||||
|
RCBitrateMode RCModeEnum = C.RC_BITRATE_MODE ///< bitrate mode
|
||||||
|
RCBufferbaseedMode RCModeEnum = C.RC_BUFFERBASED_MODE ///< no bitrate control,only using buffer status,adjust the video quality
|
||||||
|
RCTimestampMode RCModeEnum = C.RC_TIMESTAMP_MODE //rate control based timestamp
|
||||||
|
RCBitrateModePostSkip RCModeEnum = C.RC_BITRATE_MODE_POST_SKIP ///< this is in-building RC MODE, WILL BE DELETED after algorithm tuning!
|
||||||
|
RCOffMode RCModeEnum = C.RC_OFF_MODE ///< rate control off mode
|
||||||
|
)
|
||||||
|
|
||||||
|
type SliceModeEnum uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
SMSingleSlice SliceModeEnum = C.SM_SINGLE_SLICE ///< | SliceNum==1
|
||||||
|
SMFixedslcnumSlice SliceModeEnum = C.SM_FIXEDSLCNUM_SLICE ///< | according to SliceNum | enabled dynamic slicing for multi-thread
|
||||||
|
SMRasterSlice SliceModeEnum = C.SM_RASTER_SLICE ///< | according to SlicesAssign | need input of MB numbers each slice. In addition, if other constraint in SSliceArgument is presented, need to follow the constraints. Typically if MB num and slice size are both constrained, re-encoding may be involved.
|
||||||
|
SMSizelimitedSlice SliceModeEnum = C.SM_SIZELIMITED_SLICE ///< | according to SliceSize | slicing according to size, the slicing will be dynamic(have no idea about slice_nums until encoding current frame)
|
||||||
|
)
|
||||||
|
|
||||||
// NewParams returns default openh264 codec specific parameters.
|
// NewParams returns default openh264 codec specific parameters.
|
||||||
func NewParams() (Params, error) {
|
func NewParams() (Params, error) {
|
||||||
return Params{
|
return Params{
|
||||||
BaseParams: codec.BaseParams{
|
BaseParams: codec.BaseParams{
|
||||||
BitRate: 100000,
|
BitRate: 100000,
|
||||||
},
|
},
|
||||||
|
UsageType: CameraVideoRealTime,
|
||||||
|
RCMode: RCBitrateMode,
|
||||||
|
EnableFrameSkip: true,
|
||||||
|
MaxNalSize: 0,
|
||||||
|
IntraPeriod: 30,
|
||||||
|
MultipleThreadIdc: 0, // Defaults to 0, so that it'll automatically use multi threads when needed
|
||||||
|
SliceNum: 1, // Defaults to single NAL unit mode
|
||||||
|
SliceMode: SMSizelimitedSlice,
|
||||||
|
SliceSizeConstraint: 12800,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
pkg/codec/opus/Makefile
Normal file
31
pkg/codec/opus/Makefile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
git_url := https://github.com/xiph/opus.git
|
||||||
|
version := v1.3.1
|
||||||
|
src_root_dir := src
|
||||||
|
lib_dir := lib
|
||||||
|
include_dir := include
|
||||||
|
lib_prefix := libopus
|
||||||
|
src_dir := $(src_root_dir)/$(MEDIADEVICES_TARGET_PLATFORM)
|
||||||
|
output_path := $(lib_dir)/$(lib_prefix)-$(MEDIADEVICES_TARGET_PLATFORM).a
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: guard-MEDIADEVICES_TARGET_PLATFORM guard-MEDIADEVICES_TOOLCHAIN_BIN $(output_path) headers
|
||||||
|
|
||||||
|
headers: | $(src_dir) $(include_dir)
|
||||||
|
@cp $(src_dir)/include/*.h $(include_dir)
|
||||||
|
|
||||||
|
$(output_path): $(src_dir)/$(lib_prefix).a | $(lib_dir)
|
||||||
|
@cp $< $@
|
||||||
|
|
||||||
|
$(src_dir)/$(lib_prefix).a: | $(src_dir)
|
||||||
|
cd $(src_dir) && \
|
||||||
|
$(MEDIADEVICES_TOOLCHAIN_BIN) cmake -DOPUS_STACK_PROTECTOR=OFF -DCMAKE_C_FLAGS="-fpic" && \
|
||||||
|
$(MEDIADEVICES_TOOLCHAIN_BIN) make VERBOSE=1
|
||||||
|
|
||||||
|
$(src_dir): | $(src_root_dir)
|
||||||
|
git clone --depth=1 --branch=$(version) $(git_url) $@
|
||||||
|
|
||||||
|
$(src_root_dir) $(lib_dir) $(include_dir):
|
||||||
|
@mkdir -p $@
|
||||||
|
|
||||||
|
guard-%:
|
||||||
|
@if [ -z ${$*} ]; then echo "$* is a required environment variable"; exit 1; fi
|
@@ -1,19 +0,0 @@
|
|||||||
GIT_URL=https://github.com/xiph/opus.git
|
|
||||||
VERSION=v1.3.1
|
|
||||||
SRC_DIR=src
|
|
||||||
LIB_DIR=lib
|
|
||||||
INCLUDE_DIR=include
|
|
||||||
ROOT_DIR=${PWD}
|
|
||||||
LIB_PREFIX=libopus
|
|
||||||
|
|
||||||
mkdir -p ${LIB_DIR} ${INCLUDE_DIR}
|
|
||||||
|
|
||||||
git clone --depth=1 --branch=${VERSION} ${GIT_URL} ${SRC_DIR}
|
|
||||||
cd ${SRC_DIR}
|
|
||||||
${MEDIADEVICES_TOOLCHAIN_BIN} cmake -DOPUS_STACK_PROTECTOR=OFF .
|
|
||||||
${MEDIADEVICES_TOOLCHAIN_BIN} make -j2
|
|
||||||
mv ${LIB_PREFIX}.a ${ROOT_DIR}/${LIB_DIR}/${LIB_PREFIX}_${MEDIADEVICES_TARGET_PLATFORM}.a
|
|
||||||
mkdir -p ${ROOT_DIR}/${INCLUDE_DIR}
|
|
||||||
cp include/*.h ${ROOT_DIR}/${INCLUDE_DIR}
|
|
||||||
git clean -dfx
|
|
||||||
git reset --hard
|
|
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.
Binary file not shown.
Binary file not shown.
BIN
pkg/codec/opus/lib/libopus-linux-armv7.a
Normal file
BIN
pkg/codec/opus/lib/libopus-linux-armv7.a
Normal file
Binary file not shown.
BIN
pkg/codec/opus/lib/libopus-windows-x64.a
Normal file
BIN
pkg/codec/opus/lib/libopus-windows-x64.a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,8 @@ package opus
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
/*
|
/*
|
||||||
#include <opus.h>
|
#include <opus.h>
|
||||||
|
|
||||||
int bridge_encoder_set_bitrate(OpusEncoder *e, opus_int32 bitrate)
|
int pion_set_encoder_bitrate(OpusEncoder *e, opus_int32 bitrate)
|
||||||
{
|
{
|
||||||
return opus_encoder_ctl(e, OPUS_SET_BITRATE(bitrate));
|
return opus_encoder_ctl(e, OPUS_SET_BITRATE(bitrate));
|
||||||
}
|
}
|
||||||
@@ -26,9 +27,9 @@ type encoder struct {
|
|||||||
inBuff wave.Audio
|
inBuff wave.Audio
|
||||||
reader audio.Reader
|
reader audio.Reader
|
||||||
engine *C.OpusEncoder
|
engine *C.OpusEncoder
|
||||||
}
|
|
||||||
|
|
||||||
var latencies = []float64{5, 10, 20, 40, 60}
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
|
func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
|
||||||
var cerror C.int
|
var cerror C.int
|
||||||
@@ -37,10 +38,6 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
|||||||
return nil, fmt.Errorf("opus: inProp.SampleRate is required")
|
return nil, fmt.Errorf("opus: inProp.SampleRate is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Latency == 0 {
|
|
||||||
p.Latency = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.BitRate == 0 {
|
if params.BitRate == 0 {
|
||||||
params.BitRate = 32000
|
params.BitRate = 32000
|
||||||
}
|
}
|
||||||
@@ -49,19 +46,8 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
|||||||
params.ChannelMixer = &mixer.MonoMixer{}
|
params.ChannelMixer = &mixer.MonoMixer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the nearest supported latency
|
if !params.Latency.Validate() {
|
||||||
var targetLatency float64
|
return nil, fmt.Errorf("opus: unsupported latency %v", params.Latency)
|
||||||
// TODO: use p.Latency.Milliseconds() after Go 1.12 EOL
|
|
||||||
latencyInMS := float64(p.Latency.Nanoseconds() / 1000000)
|
|
||||||
nearestDist := math.Inf(+1)
|
|
||||||
for _, latency := range latencies {
|
|
||||||
dist := math.Abs(latency - latencyInMS)
|
|
||||||
if dist >= nearestDist {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
nearestDist = dist
|
|
||||||
targetLatency = latency
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channels := p.ChannelCount
|
channels := p.ChannelCount
|
||||||
@@ -77,7 +63,7 @@ func newEncoder(r audio.Reader, p prop.Media, params Params) (codec.ReadCloser,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rMix := audio.NewChannelMixer(channels, params.ChannelMixer)
|
rMix := audio.NewChannelMixer(channels, params.ChannelMixer)
|
||||||
rBuf := audio.NewBuffer(int(targetLatency * float64(p.SampleRate) / 1000))
|
rBuf := audio.NewBuffer(params.Latency.samples(p.SampleRate))
|
||||||
e := encoder{
|
e := encoder{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
reader: rMix(rBuf(r)),
|
reader: rMix(rBuf(r)),
|
||||||
@@ -97,6 +83,12 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
if e.engine == nil {
|
||||||
|
return nil, nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
encoded := make([]byte, 1024)
|
encoded := make([]byte, 1024)
|
||||||
var n C.opus_int32
|
var n C.opus_int32
|
||||||
switch b := buff.(type) {
|
switch b := buff.(type) {
|
||||||
@@ -128,7 +120,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) SetBitRate(bitRate int) error {
|
func (e *encoder) SetBitRate(bitRate int) error {
|
||||||
cerror := C.bridge_encoder_set_bitrate(
|
cerror := C.pion_set_encoder_bitrate(
|
||||||
e.engine,
|
e.engine,
|
||||||
C.int(bitRate),
|
C.int(bitRate),
|
||||||
)
|
)
|
||||||
@@ -139,11 +131,16 @@ func (e *encoder) SetBitRate(bitRate int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) ForceKeyFrame() error {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
panic("ForceKeyFrame is not implemented")
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) Close() error {
|
func (e *encoder) Close() error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
if e.engine == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
C.opus_encoder_destroy(e.engine)
|
C.opus_encoder_destroy(e.engine)
|
||||||
e.engine = nil
|
e.engine = nil
|
||||||
return nil
|
return nil
|
||||||
|
@@ -4,9 +4,10 @@ package opus
|
|||||||
|
|
||||||
//#cgo CFLAGS: -I${SRCDIR}/include
|
//#cgo CFLAGS: -I${SRCDIR}/include
|
||||||
//#cgo CXXFLAGS: -I${SRCDIR}/include
|
//#cgo CXXFLAGS: -I${SRCDIR}/include
|
||||||
//#cgo linux,arm LDFLAGS: ${SRCDIR}/lib/libopus_linux_armv7.a -lm
|
//#cgo linux,arm LDFLAGS: ${SRCDIR}/lib/libopus-linux-armv7.a -lm
|
||||||
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus_linux_arm64.a -lm
|
//#cgo linux,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-linux-arm64.a -lm
|
||||||
//#cgo linux,amd64 LDFLAGS: ${SRCDIR}/lib/libopus_linux_x64.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,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-x64.a
|
||||||
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus_windows_x64.a
|
//#cgo darwin,arm64 LDFLAGS: ${SRCDIR}/lib/libopus-darwin-arm64.a
|
||||||
|
//#cgo windows,amd64 LDFLAGS: ${SRCDIR}/lib/libopus-windows-x64.a
|
||||||
import "C"
|
import "C"
|
||||||
|
79
pkg/codec/opus/opus_test.go
Normal file
79
pkg/codec/opus/opus_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := NewParams()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
codectest.AudioEncoderReadAfterCloseTest(t, &p,
|
||||||
|
prop.Media{
|
||||||
|
Audio: prop.Audio{
|
||||||
|
SampleRate: 48000,
|
||||||
|
ChannelCount: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wave.NewInt16Interleaved(wave.ChunkInfo{
|
||||||
|
Len: 960,
|
||||||
|
SamplingRate: 48000,
|
||||||
|
Channels: 2,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
@@ -1,27 +1,69 @@
|
|||||||
package opus
|
package opus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
"github.com/pion/mediadevices/pkg/wave/mixer"
|
"github.com/pion/mediadevices/pkg/wave/mixer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Latency is a type of OPUS codec frame duration.
|
||||||
|
type Latency time.Duration
|
||||||
|
|
||||||
|
// Latency values available in OPUS codec.
|
||||||
|
const (
|
||||||
|
Latency2500us Latency = Latency(2500 * time.Microsecond)
|
||||||
|
Latency5ms Latency = Latency(5 * time.Millisecond)
|
||||||
|
Latency10ms Latency = Latency(10 * time.Millisecond)
|
||||||
|
Latency20ms Latency = Latency(20 * time.Millisecond)
|
||||||
|
Latency40ms Latency = Latency(40 * time.Millisecond)
|
||||||
|
Latency60ms Latency = Latency(60 * time.Millisecond)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate that the Latency is allowed in OPUS.
|
||||||
|
func (l Latency) Validate() bool {
|
||||||
|
switch l {
|
||||||
|
case Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration returns latency in time.Duration.
|
||||||
|
func (l Latency) Duration() time.Duration {
|
||||||
|
return time.Duration(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// samples returns number of samples for given sample rate.
|
||||||
|
func (l Latency) samples(sampleRate int) int {
|
||||||
|
return int(l.Duration() * time.Duration(sampleRate) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
// Params stores opus specific encoding parameters.
|
// Params stores opus specific encoding parameters.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
codec.BaseParams
|
codec.BaseParams
|
||||||
// ChannelMixer is a mixer to be used if number of given and expected channels differ.
|
// ChannelMixer is a mixer to be used if number of given and expected channels differ.
|
||||||
ChannelMixer mixer.ChannelMixer
|
ChannelMixer mixer.ChannelMixer
|
||||||
|
|
||||||
|
// Expected latency of the codec.
|
||||||
|
Latency Latency
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParams returns default opus codec specific parameters.
|
// NewParams returns default opus codec specific parameters.
|
||||||
func NewParams() (Params, error) {
|
func NewParams() (Params, error) {
|
||||||
return Params{}, nil
|
return Params{
|
||||||
|
Latency: Latency20ms,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RTPCodec represents the codec metadata
|
// RTPCodec represents the codec metadata
|
||||||
func (p *Params) RTPCodec() *codec.RTPCodec {
|
func (p *Params) RTPCodec() *codec.RTPCodec {
|
||||||
return codec.NewRTPOpusCodec(48000)
|
c := codec.NewRTPOpusCodec(48000)
|
||||||
|
c.Latency = time.Duration(p.Latency)
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildAudioEncoder builds opus encoder with given params
|
// BuildAudioEncoder builds opus encoder with given params
|
||||||
|
49
pkg/codec/opus/params_test.go
Normal file
49
pkg/codec/opus/params_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package opus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLatency_Validate(t *testing.T) {
|
||||||
|
t.Run("Valid", func(t *testing.T) {
|
||||||
|
for _, l := range []Latency{
|
||||||
|
Latency2500us, Latency5ms, Latency10ms, Latency20ms, Latency40ms, Latency60ms,
|
||||||
|
} {
|
||||||
|
if !l.Validate() {
|
||||||
|
t.Errorf("Defined Latency(%v) must be valid", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
for _, l := range []Latency{
|
||||||
|
0, Latency(time.Second),
|
||||||
|
} {
|
||||||
|
if l.Validate() {
|
||||||
|
t.Errorf("Latency(%v) must be valid", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLatency_samples(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
latency Latency
|
||||||
|
sampleRate int
|
||||||
|
samples int
|
||||||
|
}{
|
||||||
|
{Latency5ms, 48000, 240},
|
||||||
|
{Latency20ms, 16000, 320},
|
||||||
|
{Latency20ms, 48000, 960},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(fmt.Sprintf("%v_%d", time.Duration(testCase.latency), testCase.sampleRate), func(t *testing.T) {
|
||||||
|
samples := testCase.latency.samples(testCase.sampleRate)
|
||||||
|
if samples != testCase.samples {
|
||||||
|
t.Errorf("Expected samples: %d, got: %d", testCase.samples, samples)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
// Package vaapi implements hardware accelerated codecs.
|
// Package vaapi implements hardware accelerated codecs.
|
||||||
|
89
pkg/codec/vaapi/vaapi_test.go
Normal file
89
pkg/codec/vaapi/vaapi_test.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
codectest.VideoEncoderReadAfterCloseTest(t, p,
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package vaapi
|
package vaapi
|
||||||
@@ -61,6 +62,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
@@ -99,6 +101,8 @@ type encoderVP8 struct {
|
|||||||
|
|
||||||
rate *framerateDetector
|
rate *framerateDetector
|
||||||
|
|
||||||
|
forceKeyFrame atomic.Bool
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
@@ -303,10 +307,11 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||||
@@ -314,7 +319,7 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
|||||||
|
|
||||||
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
||||||
|
|
||||||
if kf {
|
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
|
||||||
// Key frame
|
// Key frame
|
||||||
C.setForceKFFlagVP8(&e.picParam, 1)
|
C.setForceKFFlagVP8(&e.picParam, 1)
|
||||||
C.setFrameTypeFlagVP8(&e.picParam, 0)
|
C.setFrameTypeFlagVP8(&e.picParam, 0)
|
||||||
@@ -540,18 +545,22 @@ func (e *encoderVP8) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, err
|
return encoded, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP8) SetBitRate(b int) error {
|
func (e *encoderVP8) Controller() codec.EncoderController {
|
||||||
panic("SetBitRate is not implemented")
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP8) ForceKeyFrame() error {
|
func (e *encoderVP8) ForceKeyFrame() {
|
||||||
panic("ForceKeyFrame is not implemented")
|
e.forceKeyFrame.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP8) Close() error {
|
func (e *encoderVP8) Close() error {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if e.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
||||||
C.vaDestroyContext(e.display, e.ctxID)
|
C.vaDestroyContext(e.display, e.ctxID)
|
||||||
C.vaDestroyConfig(e.display, e.confID)
|
C.vaDestroyConfig(e.display, e.confID)
|
||||||
|
25
pkg/codec/vaapi/vp8_test.go
Normal file
25
pkg/codec/vaapi/vp8_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//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 TestVP8ShouldImplementBitRateControl(t *testing.T) {
|
||||||
|
t.SkipNow() // TODO: Implement bit rate control
|
||||||
|
|
||||||
|
e := &encoderVP8{}
|
||||||
|
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVP8ShouldImplementKeyFrameControl(t *testing.T) {
|
||||||
|
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
|
// +build dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
package vaapi
|
package vaapi
|
||||||
@@ -44,6 +45,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
@@ -91,6 +93,8 @@ type encoderVP9 struct {
|
|||||||
|
|
||||||
rate *framerateDetector
|
rate *framerateDetector
|
||||||
|
|
||||||
|
forceKeyFrame atomic.Bool
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
@@ -292,10 +296,11 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
kf := e.frameCnt%e.params.KeyFrameInterval == 0
|
||||||
@@ -303,7 +308,7 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
|||||||
|
|
||||||
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
e.frParam.data.framerate = C.uint(e.rate.Calc())
|
||||||
|
|
||||||
if kf {
|
if kf || e.forceKeyFrame.CompareAndSwap(true, false) {
|
||||||
C.setForceKFFlag9(&e.picParam, 1)
|
C.setForceKFFlag9(&e.picParam, 1)
|
||||||
C.setFrameTypeFlagVP9(&e.picParam, 0)
|
C.setFrameTypeFlagVP9(&e.picParam, 0)
|
||||||
e.picParam.refresh_frame_flags = 0
|
e.picParam.refresh_frame_flags = 0
|
||||||
@@ -475,18 +480,22 @@ func (e *encoderVP9) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, err
|
return encoded, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP9) SetBitRate(b int) error {
|
func (e *encoderVP9) Controller() codec.EncoderController {
|
||||||
panic("SetBitRate is not implemented")
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP9) ForceKeyFrame() error {
|
func (e *encoderVP9) ForceKeyFrame() {
|
||||||
panic("ForceKeyFrame is not implemented")
|
e.forceKeyFrame.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoderVP9) Close() error {
|
func (e *encoderVP9) Close() error {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if e.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
|
||||||
C.vaDestroyContext(e.display, e.ctxID)
|
C.vaDestroyContext(e.display, e.ctxID)
|
||||||
C.vaDestroyConfig(e.display, e.confID)
|
C.vaDestroyConfig(e.display, e.confID)
|
||||||
|
25
pkg/codec/vaapi/vp9_test.go
Normal file
25
pkg/codec/vaapi/vp9_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//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 TestVP9ShouldImplementBitRateControl(t *testing.T) {
|
||||||
|
t.SkipNow() // TODO: Implement bit rate control
|
||||||
|
|
||||||
|
e := &encoderVP9{}
|
||||||
|
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVP9ShouldImplementKeyFrameControl(t *testing.T) {
|
||||||
|
e := &encoderVP9{}
|
||||||
|
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,7 @@ type Params struct {
|
|||||||
RateControlOvershootPercent uint
|
RateControlOvershootPercent uint
|
||||||
RateControlMinQuantizer uint
|
RateControlMinQuantizer uint
|
||||||
RateControlMaxQuantizer uint
|
RateControlMaxQuantizer uint
|
||||||
|
LagInFrames uint
|
||||||
ErrorResilient ErrorResilientMode
|
ErrorResilient ErrorResilientMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,9 @@ package vpx
|
|||||||
// int pktSz(vpx_codec_cx_pkt_t *pkt) {
|
// int pktSz(vpx_codec_cx_pkt_t *pkt) {
|
||||||
// return pkt->data.frame.sz;
|
// return pkt->data.frame.sz;
|
||||||
// }
|
// }
|
||||||
|
// vpx_codec_frame_flags_t pktFrameFlags(vpx_codec_cx_pkt_t *pkt) {
|
||||||
|
// return pkt->data.frame.flags;
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// // Alloc helpers
|
// // Alloc helpers
|
||||||
// vpx_codec_ctx_t *newCtx() {
|
// vpx_codec_ctx_t *newCtx() {
|
||||||
@@ -70,6 +73,8 @@ type encoder struct {
|
|||||||
tLastFrame int
|
tLastFrame int
|
||||||
frame []byte
|
frame []byte
|
||||||
deadline int
|
deadline int
|
||||||
|
requireKeyFrame bool
|
||||||
|
isKeyFrame bool
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
@@ -141,6 +146,7 @@ func newParams(codecIface *C.vpx_codec_iface_t) (Params, error) {
|
|||||||
RateControlOvershootPercent: uint(cfg.rc_overshoot_pct),
|
RateControlOvershootPercent: uint(cfg.rc_overshoot_pct),
|
||||||
RateControlMinQuantizer: uint(cfg.rc_min_quantizer),
|
RateControlMinQuantizer: uint(cfg.rc_min_quantizer),
|
||||||
RateControlMaxQuantizer: uint(cfg.rc_max_quantizer),
|
RateControlMaxQuantizer: uint(cfg.rc_max_quantizer),
|
||||||
|
LagInFrames: uint(cfg.g_lag_in_frames),
|
||||||
ErrorResilient: ErrorResilientMode(cfg.g_error_resilient),
|
ErrorResilient: ErrorResilientMode(cfg.g_error_resilient),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -171,6 +177,7 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
|
|||||||
cfg.g_h = C.uint(p.Height)
|
cfg.g_h = C.uint(p.Height)
|
||||||
cfg.g_timebase.num = 1
|
cfg.g_timebase.num = 1
|
||||||
cfg.g_timebase.den = 1000
|
cfg.g_timebase.den = 1000
|
||||||
|
cfg.g_lag_in_frames = C.uint(params.LagInFrames)
|
||||||
cfg.rc_target_bitrate = C.uint(params.BitRate) / 1000
|
cfg.rc_target_bitrate = C.uint(params.BitRate) / 1000
|
||||||
cfg.kf_max_dist = C.uint(params.KeyFrameInterval)
|
cfg.kf_max_dist = C.uint(params.KeyFrameInterval)
|
||||||
|
|
||||||
@@ -212,10 +219,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
bounds := yuvImg.Bounds()
|
bounds := yuvImg.Bounds()
|
||||||
height := C.int(bounds.Dy())
|
height := C.int(bounds.Dy())
|
||||||
@@ -229,9 +237,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
|
|
||||||
if e.cfg.g_w != C.uint(width) || e.cfg.g_h != C.uint(height) {
|
if e.cfg.g_w != C.uint(width) || e.cfg.g_h != C.uint(height) {
|
||||||
e.cfg.g_w, e.cfg.g_h = C.uint(width), C.uint(height)
|
e.cfg.g_w, e.cfg.g_h = C.uint(width), C.uint(height)
|
||||||
if ec := C.vpx_codec_enc_config_set(e.codec, e.cfg); ec != C.VPX_CODEC_OK {
|
|
||||||
return nil, func() {}, fmt.Errorf("vpx_codec_enc_config_set failed (%d)", ec)
|
newCodec := C.newCtx()
|
||||||
|
if ec := C.vpx_codec_enc_init_ver(
|
||||||
|
newCodec, e.codec.iface, e.cfg, 0, C.VPX_ENCODER_ABI_VERSION,
|
||||||
|
); ec != 0 {
|
||||||
|
return nil, func() {}, fmt.Errorf("vpx_codec_enc_init failed (%d)", ec)
|
||||||
}
|
}
|
||||||
|
C.free(unsafe.Pointer(e.codec))
|
||||||
|
e.codec = newCodec
|
||||||
|
|
||||||
e.raw.w, e.raw.h = C.uint(width), C.uint(height)
|
e.raw.w, e.raw.h = C.uint(width), C.uint(height)
|
||||||
e.raw.r_w, e.raw.r_h = C.uint(width), C.uint(height)
|
e.raw.r_w, e.raw.r_h = C.uint(width), C.uint(height)
|
||||||
e.raw.d_w, e.raw.d_h = C.uint(width), C.uint(height)
|
e.raw.d_w, e.raw.d_h = C.uint(width), C.uint(height)
|
||||||
@@ -247,6 +262,9 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
duration = 1
|
duration = 1
|
||||||
}
|
}
|
||||||
var flags int
|
var flags int
|
||||||
|
if e.requireKeyFrame {
|
||||||
|
flags = flags | C.VPX_EFLAG_FORCE_KF
|
||||||
|
}
|
||||||
if ec := C.encode_wrapper(
|
if ec := C.encode_wrapper(
|
||||||
e.codec, e.raw,
|
e.codec, e.raw,
|
||||||
C.long(t-e.tStart), C.ulong(duration), C.long(flags), C.ulong(e.deadline),
|
C.long(t-e.tStart), C.ulong(duration), C.long(flags), C.ulong(e.deadline),
|
||||||
@@ -255,6 +273,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, fmt.Errorf("vpx_codec_encode failed (%d)", ec)
|
return nil, func() {}, fmt.Errorf("vpx_codec_encode failed (%d)", ec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.requireKeyFrame = false
|
||||||
e.frameIndex++
|
e.frameIndex++
|
||||||
e.tLastFrame = t
|
e.tLastFrame = t
|
||||||
|
|
||||||
@@ -266,6 +285,7 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if pkt.kind == C.VPX_CODEC_CX_FRAME_PKT {
|
if pkt.kind == C.VPX_CODEC_CX_FRAME_PKT {
|
||||||
|
e.isKeyFrame = C.pktFrameFlags(pkt)&C.VPX_FRAME_IS_KEY == C.VPX_FRAME_IS_KEY
|
||||||
encoded := C.GoBytes(unsafe.Pointer(C.pktBuf(pkt)), C.pktSz(pkt))
|
encoded := C.GoBytes(unsafe.Pointer(C.pktBuf(pkt)), C.pktSz(pkt))
|
||||||
e.frame = append(e.frame, encoded...)
|
e.frame = append(e.frame, encoded...)
|
||||||
}
|
}
|
||||||
@@ -276,18 +296,25 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, err
|
return encoded, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) SetBitRate(b int) error {
|
func (e *encoder) ForceKeyFrame() error {
|
||||||
panic("SetBitRate is not implemented")
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.requireKeyFrame = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) ForceKeyFrame() error {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
panic("ForceKeyFrame is not implemented")
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) Close() error {
|
func (e *encoder) Close() error {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if e.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
e.closed = true
|
e.closed = true
|
||||||
|
|
||||||
C.free(unsafe.Pointer(e.raw))
|
C.free(unsafe.Pointer(e.raw))
|
||||||
|
247
pkg/codec/vpx/vpx_test.go
Normal file
247
pkg/codec/vpx/vpx_test.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
package vpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
codectest.VideoEncoderReadAfterCloseTest(t, p,
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageSizeChange(t *testing.T) {
|
||||||
|
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
|
||||||
|
"VP8": func() (codec.VideoEncoderBuilder, error) {
|
||||||
|
p, err := NewVP8Params()
|
||||||
|
return &p, err
|
||||||
|
},
|
||||||
|
"VP9": func() (codec.VideoEncoderBuilder, error) {
|
||||||
|
p, err := NewVP9Params()
|
||||||
|
// Disable latency to ease test and begin to receive packets for each input frame
|
||||||
|
p.LagInFrames = 0
|
||||||
|
return &p, err
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
factory := factory
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
param, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range map[string]struct {
|
||||||
|
initialWidth, initialHeight int
|
||||||
|
width, height int
|
||||||
|
}{
|
||||||
|
"NoChange": {
|
||||||
|
320, 240,
|
||||||
|
320, 240,
|
||||||
|
},
|
||||||
|
"Enlarge": {
|
||||||
|
320, 240,
|
||||||
|
640, 480,
|
||||||
|
},
|
||||||
|
"Shrink": {
|
||||||
|
640, 480,
|
||||||
|
320, 240,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var cnt uint32
|
||||||
|
r, err := param.BuildVideoEncoder(
|
||||||
|
video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
i := atomic.AddUint32(&cnt, 1)
|
||||||
|
if i == 1 {
|
||||||
|
return image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, testCase.width, testCase.height),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
), func() {}, nil
|
||||||
|
}
|
||||||
|
return nil, nil, io.EOF
|
||||||
|
}),
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: testCase.initialWidth,
|
||||||
|
Height: testCase.initialHeight,
|
||||||
|
FrameRate: 1,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, rel, err := r.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rel()
|
||||||
|
_, _, err = r.Read()
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestKeyFrame(t *testing.T) {
|
||||||
|
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
|
||||||
|
"VP8": func() (codec.VideoEncoderBuilder, error) {
|
||||||
|
p, err := NewVP8Params()
|
||||||
|
return &p, err
|
||||||
|
},
|
||||||
|
"VP9": func() (codec.VideoEncoderBuilder, error) {
|
||||||
|
p, err := NewVP9Params()
|
||||||
|
// Disable latency to ease test and begin to receive packets for each input frame
|
||||||
|
p.LagInFrames = 0
|
||||||
|
return &p, err
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
factory := factory
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
param, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialWidth, initialHeight, width, height int = 320, 240, 320, 240
|
||||||
|
|
||||||
|
var cnt uint32
|
||||||
|
r, err := param.BuildVideoEncoder(
|
||||||
|
video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
i := atomic.AddUint32(&cnt, 1)
|
||||||
|
if i == 3 {
|
||||||
|
return nil, nil, io.EOF
|
||||||
|
}
|
||||||
|
return image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, width, height),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
), func() {}, nil
|
||||||
|
}),
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: initialWidth,
|
||||||
|
Height: initialHeight,
|
||||||
|
FrameRate: 1,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, rel, err := r.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rel()
|
||||||
|
r.Controller().(codec.KeyFrameController).ForceKeyFrame()
|
||||||
|
_, rel, err = r.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !r.(*encoder).isKeyFrame {
|
||||||
|
t.Fatal("Not a key frame")
|
||||||
|
}
|
||||||
|
rel()
|
||||||
|
_, _, err = r.Read()
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_t *h;
|
||||||
x264_picture_t pic_in;
|
x264_picture_t pic_in;
|
||||||
x264_param_t param;
|
x264_param_t param;
|
||||||
|
int force_key_frame;
|
||||||
} Encoder;
|
} Encoder;
|
||||||
|
|
||||||
Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
|
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[0] = y;
|
||||||
e->pic_in.img.plane[1] = cb;
|
e->pic_in.img.plane[1] = cb;
|
||||||
e->pic_in.img.plane[2] = cr;
|
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);
|
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};
|
Slice s = {.data_len = frame_size};
|
||||||
if (frame_size <= 0) {
|
if (frame_size <= 0) {
|
||||||
*rc = ERR_ENCODE;
|
*rc = ERR_ENCODE;
|
||||||
|
@@ -102,10 +102,11 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := e.r.Read()
|
img, release, err := e.r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
defer release()
|
||||||
yuvImg := img.(*image.YCbCr)
|
yuvImg := img.(*image.YCbCr)
|
||||||
|
|
||||||
var rc C.int
|
var rc C.int
|
||||||
@@ -124,12 +125,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
return encoded, func() {}, err
|
return encoded, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) SetBitRate(b int) error {
|
// TODO: Implement bit rate control
|
||||||
panic("SetBitRate is not implemented")
|
//var _ codec.BitRateController = (*encoder)(nil)
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoder) ForceKeyFrame() error {
|
func (e *encoder) ForceKeyFrame() error {
|
||||||
panic("ForceKeyFrame is not implemented")
|
e.engine.force_key_frame = C.int(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) Close() error {
|
func (e *encoder) Close() error {
|
||||||
|
87
pkg/codec/x264/x264_test.go
Normal file
87
pkg/codec/x264/x264_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("ReadAfterClose", func(t *testing.T) {
|
||||||
|
p, err := NewParams()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p.BitRate = 200000
|
||||||
|
codectest.VideoEncoderReadAfterCloseTest(t, &p,
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||||
|
t.SkipNow() // TODO: Implement key frame control
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
28
pkg/driver/availability/error.go
Normal file
28
pkg/driver/availability/error.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package availability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnimplemented = NewError("not implemented")
|
||||||
|
ErrBusy = NewError("device or resource busy")
|
||||||
|
ErrNoDevice = NewError("no such device")
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorString struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(text string) error {
|
||||||
|
return &errorString{text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsError(err error) bool {
|
||||||
|
var target *errorString
|
||||||
|
return errors.As(err, &target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorString) Error() string {
|
||||||
|
return e.s
|
||||||
|
}
|
@@ -1 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Package camera provides a video camera driver.
|
||||||
|
|
||||||
|
Device Label Generation Rules
|
||||||
|
|
||||||
|
On Linux, the device label will be in the format of:
|
||||||
|
pci-0000:00:00.0-usb-0:0:0.0-video-index0;video0
|
||||||
|
If /dev/v4l/by-path/* is not available (for example in a docker container without
|
||||||
|
bindings in /dev/v4l/by-path/), it will be:
|
||||||
|
video0;video0
|
||||||
|
*/
|
||||||
package camera
|
package camera
|
||||||
|
|
||||||
|
// LabelSeparator is used to separate labels for a driver that
|
||||||
|
// is found from multiple locations on a host.
|
||||||
|
const LabelSeparator = ";"
|
||||||
|
@@ -56,7 +56,7 @@ func BenchmarkRead(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := r.Read()
|
_, _, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to read: %v", err)
|
b.Fatalf("Failed to read: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -13,9 +13,15 @@ import (
|
|||||||
type camera struct {
|
type camera struct {
|
||||||
device avfoundation.Device
|
device avfoundation.Device
|
||||||
session *avfoundation.Session
|
session *avfoundation.Session
|
||||||
|
rcClose func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
devices, err := avfoundation.Devices(avfoundation.Video)
|
devices, err := avfoundation.Devices(avfoundation.Video)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -26,6 +32,7 @@ func init() {
|
|||||||
driver.GetManager().Register(cam, driver.Info{
|
driver.GetManager().Register(cam, driver.Info{
|
||||||
Label: device.UID,
|
Label: device.UID,
|
||||||
DeviceType: driver.Camera,
|
DeviceType: driver.Camera,
|
||||||
|
Name: device.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +50,9 @@ func (cam *camera) Open() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cam *camera) Close() error {
|
func (cam *camera) Close() error {
|
||||||
|
if cam.rcClose != nil {
|
||||||
|
cam.rcClose()
|
||||||
|
}
|
||||||
return cam.session.Close()
|
return cam.session.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +66,7 @@ func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cam.rcClose = rc.Close
|
||||||
r := video.ReaderFunc(func() (image.Image, func(), error) {
|
r := video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
frame, _, err := rc.Read()
|
frame, _, err := rc.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
63
pkg/driver/camera/camera_darwin_test.go
Normal file
63
pkg/driver/camera/camera_darwin_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// +build darwin
|
||||||
|
|
||||||
|
// $ go test -v . -tags darwin -run="^TestCameraFrameFormatSupport$"
|
||||||
|
|
||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/avfoundation"
|
||||||
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCameraFrameFormatSupport(t *testing.T) {
|
||||||
|
devices, err := avfoundation.Devices(avfoundation.Video)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(devices) > 0 {
|
||||||
|
c := newCamera(devices[0])
|
||||||
|
if err := c.Open(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
supportedFormats := make(map[frame.Format]struct{})
|
||||||
|
for _, p := range c.Properties() {
|
||||||
|
supportedFormats[p.FrameFormat] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range []frame.Format{
|
||||||
|
frame.FormatI420,
|
||||||
|
frame.FormatNV12,
|
||||||
|
frame.FormatNV21,
|
||||||
|
frame.FormatYUY2,
|
||||||
|
frame.FormatUYVY,
|
||||||
|
} {
|
||||||
|
if _, ok := supportedFormats[format]; !ok {
|
||||||
|
t.Logf("[%v] UNSUPPORTED", format)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, err := c.VideoRecord(prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 640,
|
||||||
|
Height: 480,
|
||||||
|
FrameFormat: format,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("[%v] Failed to capture image: %v", format, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
_, _, err := r.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("[%v] Failed to read: %v", format, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("[%v] OK", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,9 +8,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||||
|
|
||||||
"github.com/blackjack/webcam"
|
"github.com/blackjack/webcam"
|
||||||
"github.com/pion/mediadevices/pkg/driver"
|
"github.com/pion/mediadevices/pkg/driver"
|
||||||
@@ -59,6 +65,8 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const bufCount = 2
|
||||||
|
|
||||||
// Camera implementation using v4l2
|
// Camera implementation using v4l2
|
||||||
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
||||||
type camera struct {
|
type camera struct {
|
||||||
@@ -69,12 +77,28 @@ type camera struct {
|
|||||||
started bool
|
started bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
cancel func()
|
cancel func()
|
||||||
|
prevFrameTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
discovered := make(map[string]struct{})
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
discover := func(pattern string) {
|
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
|
// Clear all registered camera devices to prevent duplicates.
|
||||||
|
// If first initalize call, this will be a noop.
|
||||||
|
manager := driver.GetManager()
|
||||||
|
for _, d := range manager.Query(driver.FilterVideoRecorder()) {
|
||||||
|
manager.Delete(d.ID())
|
||||||
|
}
|
||||||
|
discovered := make(map[string]struct{})
|
||||||
|
discover(discovered, "/dev/v4l/by-id/*")
|
||||||
|
discover(discovered, "/dev/v4l/by-path/*")
|
||||||
|
discover(discovered, "/dev/video*")
|
||||||
|
}
|
||||||
|
|
||||||
|
func discover(discovered map[string]struct{}, pattern string) {
|
||||||
devices, err := filepath.Glob(pattern)
|
devices, err := filepath.Glob(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No v4l device.
|
// No v4l device.
|
||||||
@@ -88,7 +112,6 @@ func init() {
|
|||||||
} else {
|
} else {
|
||||||
reallink = filepath.Base(reallink)
|
reallink = filepath.Base(reallink)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := discovered[reallink]; ok {
|
if _, ok := discovered[reallink]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -96,28 +119,40 @@ func init() {
|
|||||||
discovered[reallink] = struct{}{}
|
discovered[reallink] = struct{}{}
|
||||||
cam := newCamera(device)
|
cam := newCamera(device)
|
||||||
priority := driver.PriorityNormal
|
priority := driver.PriorityNormal
|
||||||
if label == prioritizedDevice {
|
if reallink == prioritizedDevice {
|
||||||
priority = driver.PriorityHigh
|
priority = driver.PriorityHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var name, busInfo string
|
||||||
|
if webcamCam, err := webcam.Open(cam.path); err == nil {
|
||||||
|
name, _ = webcamCam.GetName()
|
||||||
|
busInfo, _ = webcamCam.GetBusInfo()
|
||||||
|
}
|
||||||
|
|
||||||
driver.GetManager().Register(cam, driver.Info{
|
driver.GetManager().Register(cam, driver.Info{
|
||||||
Label: label,
|
// Source: https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-querycap.html
|
||||||
|
// Name of the device, a NUL-terminated UTF-8 string. For example: “Yoyodyne TV/FM”. One driver may support
|
||||||
|
// different brands or models of video hardware. This information is intended for users, for example in a
|
||||||
|
// menu of available devices. Since multiple TV cards of the same brand may be installed which are
|
||||||
|
// supported by the same driver, this name should be combined with the character device file name
|
||||||
|
// (e.g. /dev/video2) or the bus_info string to avoid ambiguities.
|
||||||
|
Name: name + LabelSeparator + busInfo,
|
||||||
|
Label: label + LabelSeparator + reallink,
|
||||||
DeviceType: driver.Camera,
|
DeviceType: driver.Camera,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
discover("/dev/v4l/by-path/*")
|
|
||||||
discover("/dev/video*")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCamera(path string) *camera {
|
func newCamera(path string) *camera {
|
||||||
formats := map[webcam.PixelFormat]frame.Format{
|
formats := map[webcam.PixelFormat]frame.Format{
|
||||||
webcam.PixelFormat(C.V4L2_PIX_FMT_YUV420): frame.FormatI420,
|
webcam.PixelFormat(C.V4L2_PIX_FMT_YUV420): frame.FormatI420,
|
||||||
|
webcam.PixelFormat(C.V4L2_PIX_FMT_NV21): frame.FormatNV21,
|
||||||
|
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV12,
|
||||||
webcam.PixelFormat(C.V4L2_PIX_FMT_YUYV): frame.FormatYUYV,
|
webcam.PixelFormat(C.V4L2_PIX_FMT_YUYV): frame.FormatYUYV,
|
||||||
webcam.PixelFormat(C.V4L2_PIX_FMT_UYVY): frame.FormatUYVY,
|
webcam.PixelFormat(C.V4L2_PIX_FMT_UYVY): frame.FormatUYVY,
|
||||||
webcam.PixelFormat(C.V4L2_PIX_FMT_NV12): frame.FormatNV21,
|
|
||||||
webcam.PixelFormat(C.V4L2_PIX_FMT_MJPEG): frame.FormatMJPEG,
|
webcam.PixelFormat(C.V4L2_PIX_FMT_MJPEG): frame.FormatMJPEG,
|
||||||
|
webcam.PixelFormat(C.V4L2_PIX_FMT_Z16): frame.FormatZ16,
|
||||||
}
|
}
|
||||||
|
|
||||||
reversedFormats := make(map[frame.Format]webcam.PixelFormat)
|
reversedFormats := make(map[frame.Format]webcam.PixelFormat)
|
||||||
@@ -133,14 +168,32 @@ func newCamera(path string) *camera {
|
|||||||
return c
|
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 {
|
func (c *camera) Open() error {
|
||||||
cam, err := webcam.Open(c.path)
|
cam, err := webcam.Open(c.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Late frames should be discarded. Buffering should be handled in higher level.
|
// Buffering should be handled in higher level.
|
||||||
cam.SetBufferCount(1)
|
err = cam.SetBufferCount(bufCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.prevFrameTime = time.Now()
|
||||||
c.cam = cam
|
c.cam = cam
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -179,12 +232,21 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
|||||||
return nil, err
|
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 {
|
if err := c.cam.StartStreaming(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cam := c.cam
|
cam := c.cam
|
||||||
|
|
||||||
|
readTimeoutSec := getCameraReadTimeout()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
c.cancel = cancel
|
c.cancel = cancel
|
||||||
var buf []byte
|
var buf []byte
|
||||||
@@ -200,7 +262,14 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
|||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
err := cam.WaitForFrame(5) // 5 seconds
|
if p.DiscardFramesOlderThan != 0 && time.Now().Sub(c.prevFrameTime) >= p.DiscardFramesOlderThan {
|
||||||
|
for i := 0; i < bufCount; i++ {
|
||||||
|
_ = cam.WaitForFrame(readTimeoutSec)
|
||||||
|
_, _ = cam.ReadFrame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cam.WaitForFrame(readTimeoutSec)
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
case *webcam.Timeout:
|
case *webcam.Timeout:
|
||||||
@@ -216,6 +285,10 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
|
|||||||
return nil, func() {}, err
|
return nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.DiscardFramesOlderThan != 0 {
|
||||||
|
c.prevFrameTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
// Frame is empty.
|
// Frame is empty.
|
||||||
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
|
// Retry reading and return errEmptyFrame if it exceeds maxEmptyFrameCount.
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
@@ -249,6 +322,9 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
|
if frameSize.StepWidth == 0 || frameSize.StepHeight == 0 {
|
||||||
|
framerates := c.cam.GetSupportedFramerates(format, uint32(frameSize.MaxWidth), uint32(frameSize.MaxHeight))
|
||||||
|
// If the camera doesn't support framerate, we just add the resolution and format
|
||||||
|
if len(framerates) == 0 {
|
||||||
properties = append(properties, prop.Media{
|
properties = append(properties, prop.Media{
|
||||||
Video: prop.Video{
|
Video: prop.Video{
|
||||||
Width: int(frameSize.MaxWidth),
|
Width: int(frameSize.MaxWidth),
|
||||||
@@ -256,6 +332,21 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
FrameFormat: supportedFormat,
|
FrameFormat: supportedFormat,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, framerate := range framerates {
|
||||||
|
for _, fps := range enumFramerate(framerate) {
|
||||||
|
properties = append(properties, prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: int(frameSize.MaxWidth),
|
||||||
|
Height: int(frameSize.MaxHeight),
|
||||||
|
FrameFormat: supportedFormat,
|
||||||
|
FrameRate: fps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
|
// FIXME: we should probably use a custom data structure to capture all of the supported resolutions
|
||||||
for _, supportedResolution := range supportedResolutions {
|
for _, supportedResolution := range supportedResolutions {
|
||||||
@@ -274,6 +365,8 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
framerates := c.cam.GetSupportedFramerates(format, uint32(width), uint32(height))
|
||||||
|
if len(framerates) == 0 {
|
||||||
properties = append(properties, prop.Media{
|
properties = append(properties, prop.Media{
|
||||||
Video: prop.Video{
|
Video: prop.Video{
|
||||||
Width: width,
|
Width: width,
|
||||||
@@ -281,9 +374,91 @@ func (c *camera) Properties() []prop.Media {
|
|||||||
FrameFormat: supportedFormat,
|
FrameFormat: supportedFormat,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, framerate := range framerates {
|
||||||
|
for _, fps := range enumFramerate(framerate) {
|
||||||
|
properties = append(properties, prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
FrameFormat: supportedFormat,
|
||||||
|
FrameRate: fps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return properties
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *camera) IsAvailable() (bool, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// close the opened file descriptor as quickly as possible and in all cases, including panics
|
||||||
|
func() {
|
||||||
|
var cam *webcam.Webcam
|
||||||
|
if cam, err = webcam.Open(c.path); err == nil {
|
||||||
|
defer cam.Close()
|
||||||
|
var index int32
|
||||||
|
// "Drivers must implement all the input ioctls when the device has one or more inputs..."
|
||||||
|
// Source: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/video.html?highlight=vidioc_enuminput
|
||||||
|
if index, err = cam.GetInput(); err == nil {
|
||||||
|
err = cam.SelectInput(uint32(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errno syscall.Errno
|
||||||
|
errors.As(err, &errno)
|
||||||
|
|
||||||
|
// See https://man7.org/linux/man-pages/man3/errno.3.html
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return true, nil
|
||||||
|
case errno == syscall.EBUSY:
|
||||||
|
return false, availability.ErrBusy
|
||||||
|
case errno == syscall.ENODEV || errno == syscall.ENOENT:
|
||||||
|
return false, availability.ErrNoDevice
|
||||||
|
default:
|
||||||
|
return false, availability.NewError(errno.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumFramerate returns a list of fps options from a FrameRate struct.
|
||||||
|
// discrete framerates will return a list of 1 fps element.
|
||||||
|
// stepwise framerates will return a list of all possible fps options.
|
||||||
|
func enumFramerate(framerate webcam.FrameRate) []float32 {
|
||||||
|
var framerates []float32
|
||||||
|
if framerate.StepNumerator == 0 && framerate.StepDenominator == 0 {
|
||||||
|
fr, err := calcFramerate(framerate.MaxNumerator, framerate.MaxDenominator)
|
||||||
|
if err != nil {
|
||||||
|
return framerates
|
||||||
|
}
|
||||||
|
framerates = append(framerates, fr)
|
||||||
|
} else {
|
||||||
|
for n := framerate.MinNumerator; n <= framerate.MaxNumerator; n += framerate.StepNumerator {
|
||||||
|
for d := framerate.MinDenominator; d <= framerate.MaxDenominator; d += framerate.StepDenominator {
|
||||||
|
fr, err := calcFramerate(n, d)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
framerates = append(framerates, fr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return framerates
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcFramerate turns fraction into a float32 fps value.
|
||||||
|
func calcFramerate(numerator uint32, denominator uint32) (float32, error) {
|
||||||
|
if denominator == 0 {
|
||||||
|
return 0, errors.New("framerate denominator is zero")
|
||||||
|
}
|
||||||
|
// round to three decimal places to avoid floating point precision issues
|
||||||
|
return float32(math.Round(1000.0/((float64(numerator))/float64(denominator))) / 1000), nil
|
||||||
|
}
|
||||||
|
193
pkg/driver/camera/camera_linux_test.go
Normal file
193
pkg/driver/camera/camera_linux_test.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiscover(t *testing.T) {
|
||||||
|
const (
|
||||||
|
shortName = "unittest-video0"
|
||||||
|
shortName2 = "unittest-video1"
|
||||||
|
longName = "unittest-long-device-name:0:1:2:3"
|
||||||
|
)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
byPathDir := filepath.Join(dir, "v4l", "by-path")
|
||||||
|
if err := os.MkdirAll(byPathDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(
|
||||||
|
filepath.Join(dir, shortName),
|
||||||
|
filepath.Join(byPathDir, longName),
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
discovered := make(map[string]struct{})
|
||||||
|
discover(discovered, filepath.Join(byPathDir, "*"))
|
||||||
|
discover(discovered, filepath.Join(dir, "unittest-video*"))
|
||||||
|
|
||||||
|
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
|
||||||
|
// Ignore real cameras.
|
||||||
|
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "unittest")
|
||||||
|
})
|
||||||
|
if len(drvs) != 2 {
|
||||||
|
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{
|
||||||
|
drvs[0].Info().Label,
|
||||||
|
drvs[1].Info().Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returned drivers are unordered. Sort to get static result.
|
||||||
|
sort.Sort(sort.StringSlice(labels))
|
||||||
|
|
||||||
|
expected := longName + LabelSeparator + shortName
|
||||||
|
if label := labels[0]; label != expected {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expected, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNoLink := shortName2 + LabelSeparator + shortName2
|
||||||
|
if label := labels[1]; label != expectedNoLink {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoverByID(t *testing.T) {
|
||||||
|
const (
|
||||||
|
shortName = "id-unittest-video0"
|
||||||
|
shortName2 = "id-unittest-video1"
|
||||||
|
longName = "id-unittest-long-device-name:0:1:2:3"
|
||||||
|
)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
byIdDir := filepath.Join(dir, "v4l", "by-id")
|
||||||
|
if err := os.MkdirAll(byIdDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(dir, shortName2), []byte{}, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(
|
||||||
|
filepath.Join(dir, shortName),
|
||||||
|
filepath.Join(byIdDir, longName),
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
discovered := make(map[string]struct{})
|
||||||
|
discover(discovered, filepath.Join(byIdDir, "*"))
|
||||||
|
discover(discovered, filepath.Join(dir, "id-unittest-video*"))
|
||||||
|
|
||||||
|
drvs := driver.GetManager().Query(func(d driver.Driver) bool {
|
||||||
|
// Ignore real cameras.
|
||||||
|
return d.Info().DeviceType == driver.Camera && strings.Contains(d.Info().Label, "id-unittest")
|
||||||
|
})
|
||||||
|
if len(drvs) != 2 {
|
||||||
|
t.Fatalf("Expected 2 driver, got %d drivers", len(drvs))
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{
|
||||||
|
drvs[0].Info().Label,
|
||||||
|
drvs[1].Info().Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returned drivers are unordered. Sort to get static result.
|
||||||
|
sort.Sort(sort.StringSlice(labels))
|
||||||
|
|
||||||
|
expected := longName + LabelSeparator + shortName
|
||||||
|
if label := labels[0]; label != expected {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expected, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNoLink := shortName2 + LabelSeparator + shortName2
|
||||||
|
if label := labels[1]; label != expectedNoLink {
|
||||||
|
t.Errorf("Expected label: %s, got: %s", expectedNoLink, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalcFramerate(t *testing.T) {
|
||||||
|
framerates := []struct {
|
||||||
|
numerator uint32
|
||||||
|
denominator uint32
|
||||||
|
expected float32
|
||||||
|
}{
|
||||||
|
{1, 10, 10.0},
|
||||||
|
{1, 15, 15.0},
|
||||||
|
{1, 30, 30.0},
|
||||||
|
{1, 60, 60.0},
|
||||||
|
{1, 120, 120.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, framerate := range framerates {
|
||||||
|
value, err := calcFramerate(framerate.numerator, framerate.denominator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// make sure we do not have any rounding errors
|
||||||
|
if value != framerate.expected {
|
||||||
|
t.Errorf("Expected: %f, got: %f", framerate.expected, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// divide by zero check
|
||||||
|
_, err := calcFramerate(1, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected divide by zero error")
|
||||||
|
}
|
||||||
|
}
|
@@ -76,6 +76,7 @@ int listCamera(cameraList* list, const char** errstr)
|
|||||||
{
|
{
|
||||||
list->name[i] = getCameraName(moniker);
|
list->name[i] = getCameraName(moniker);
|
||||||
moniker->Release();
|
moniker->Release();
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,6 +32,11 @@ type camera struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize finds and registers camera devices. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
|
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
|
||||||
|
|
||||||
var list C.cameraList
|
var list C.cameraList
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pion/mediadevices/pkg/driver/availability"
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
"github.com/pion/mediadevices/pkg/io/video"
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
@@ -30,6 +31,7 @@ type Info struct {
|
|||||||
Label string
|
Label string
|
||||||
DeviceType DeviceType
|
DeviceType DeviceType
|
||||||
Priority Priority
|
Priority Priority
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Adapter interface {
|
type Adapter interface {
|
||||||
@@ -44,3 +46,14 @@ type Driver interface {
|
|||||||
Info() Info
|
Info() Info
|
||||||
Status() State
|
Status() State
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AvailabilityAdapter interface {
|
||||||
|
IsAvailable() (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAvailable(d Driver) (bool, error) {
|
||||||
|
if aa, ok := d.(AvailabilityAdapter); ok {
|
||||||
|
return aa.IsAvailable()
|
||||||
|
}
|
||||||
|
return false, availability.ErrUnimplemented
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// FilterFn is being used to decide if a driver should be included in the
|
// FilterFn is being used to decide if a driver should be included in the
|
||||||
// query result.
|
// query result.
|
||||||
type FilterFn func(Driver) bool
|
type FilterFn func(Driver) bool
|
||||||
@@ -55,6 +57,7 @@ func FilterNot(filter FilterFn) FilterFn {
|
|||||||
|
|
||||||
// Manager is a singleton to manage multiple drivers and their states
|
// Manager is a singleton to manage multiple drivers and their states
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
mu sync.Mutex
|
||||||
drivers map[string]Driver
|
drivers map[string]Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,8 @@ func GetManager() *Manager {
|
|||||||
|
|
||||||
// Register registers adapter to be discoverable by Query
|
// Register registers adapter to be discoverable by Query
|
||||||
func (m *Manager) Register(a Adapter, info Info) error {
|
func (m *Manager) Register(a Adapter, info Info) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
d := wrapAdapter(a, info)
|
d := wrapAdapter(a, info)
|
||||||
m.drivers[d.ID()] = d
|
m.drivers[d.ID()] = d
|
||||||
return nil
|
return nil
|
||||||
@@ -76,6 +81,8 @@ func (m *Manager) Register(a Adapter, info Info) error {
|
|||||||
|
|
||||||
// Query queries by using f to filter drivers, and simply return the filtered results.
|
// Query queries by using f to filter drivers, and simply return the filtered results.
|
||||||
func (m *Manager) Query(f FilterFn) []Driver {
|
func (m *Manager) Query(f FilterFn) []Driver {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
results := make([]Driver, 0)
|
results := make([]Driver, 0)
|
||||||
for _, d := range m.drivers {
|
for _, d := range m.drivers {
|
||||||
if ok := f(d); ok {
|
if ok := f(d); ok {
|
||||||
@@ -85,3 +92,10 @@ func (m *Manager) Query(f FilterFn) []Driver {
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete deletes a driver from manager given its ID
|
||||||
|
func (m *Manager) Delete(id string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
delete(m.drivers, id)
|
||||||
|
}
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func filterTrue(d Driver) bool {
|
func filterTrue(_ Driver) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func filterFalse(d Driver) bool {
|
func filterFalse(_ Driver) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,3 +44,71 @@ func TestFilterAnd(t *testing.T) {
|
|||||||
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
|
t.Error("FilterAnd(filterTrue, filterTrue, filterTrue)() must be true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeVideoAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeVideoAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeVideoAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeVideoAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func (a *fakeVideoAdapter) VideoRecord(_ prop.Media) (r video.Reader, err error) { return nil, nil }
|
||||||
|
|
||||||
|
type fakeAudioAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeAudioAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeAudioAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeAudioAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func (a *fakeAudioAdapter) AudioRecord(_ prop.Media) (r audio.Reader, err error) { return nil, nil }
|
||||||
|
|
||||||
|
type fakeAdapter struct{}
|
||||||
|
|
||||||
|
func (a *fakeAdapter) Open() error { return nil }
|
||||||
|
func (a *fakeAdapter) Close() error { return nil }
|
||||||
|
func (a *fakeAdapter) Properties() []prop.Media { return nil }
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
|
||||||
|
va := &fakeVideoAdapter{}
|
||||||
|
err := m.Register(va, Info{})
|
||||||
|
assert.NoError(t, err, "cannot register video adapter")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 1)
|
||||||
|
|
||||||
|
aa := &fakeAudioAdapter{}
|
||||||
|
err = m.Register(aa, Info{})
|
||||||
|
assert.NoError(t, err, "cannot register audio adapter")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||||
|
|
||||||
|
a := &fakeAdapter{}
|
||||||
|
assert.Panics(t, func() { m.Register(a, Info{}) }, "should not register adapter that is neither audio nor video")
|
||||||
|
assert.Equal(t, len(m.Query(filterTrue)), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterSync(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
start := make(chan struct{})
|
||||||
|
race := func() {
|
||||||
|
<-start
|
||||||
|
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
go race()
|
||||||
|
go race()
|
||||||
|
close(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySync(t *testing.T) {
|
||||||
|
m := GetManager()
|
||||||
|
start := make(chan struct{})
|
||||||
|
race := func() {
|
||||||
|
<-start
|
||||||
|
m.Query(filterTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
go race()
|
||||||
|
go race()
|
||||||
|
close(start)
|
||||||
|
// write while reading
|
||||||
|
assert.NoError(t, m.Register(&fakeVideoAdapter{}, Info{}))
|
||||||
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package microphone
|
package microphone
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -33,9 +35,15 @@ var (
|
|||||||
type microphone struct {
|
type microphone struct {
|
||||||
malgo.DeviceInfo
|
malgo.DeviceInfo
|
||||||
chunkChan chan []byte
|
chunkChan chan []byte
|
||||||
|
deviceCloseFunc func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize finds and registers active playback or capture devices. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
var err error
|
var err error
|
||||||
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
|
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
|
||||||
logger.Debugf("%v\n", message)
|
logger.Debugf("%v\n", message)
|
||||||
@@ -60,6 +68,7 @@ func init() {
|
|||||||
Label: device.ID.String(),
|
Label: device.ID.String(),
|
||||||
DeviceType: driver.Microphone,
|
DeviceType: driver.Microphone,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
|
Name: info.Name(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,9 +96,8 @@ func (m *microphone) Open() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *microphone) Close() error {
|
func (m *microphone) Close() error {
|
||||||
if m.chunkChan != nil {
|
if m.deviceCloseFunc != nil {
|
||||||
close(m.chunkChan)
|
m.deviceCloseFunc()
|
||||||
m.chunkChan = nil
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -111,6 +119,9 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
|
|||||||
config.PerformanceProfile = malgo.LowLatency
|
config.PerformanceProfile = malgo.LowLatency
|
||||||
config.Capture.Channels = uint32(inputProp.ChannelCount)
|
config.Capture.Channels = uint32(inputProp.ChannelCount)
|
||||||
config.SampleRate = uint32(inputProp.SampleRate)
|
config.SampleRate = uint32(inputProp.SampleRate)
|
||||||
|
config.PeriodSizeInMilliseconds = uint32(inputProp.Latency.Milliseconds())
|
||||||
|
//FIX: Turn on the microphone with the current device id
|
||||||
|
config.Capture.DeviceID = m.ID.Pointer()
|
||||||
if inputProp.SampleSize == 4 && inputProp.IsFloat {
|
if inputProp.SampleSize == 4 && inputProp.IsFloat {
|
||||||
config.Capture.Format = malgo.FormatF32
|
config.Capture.Format = malgo.FormatF32
|
||||||
} else if inputProp.SampleSize == 2 && !inputProp.IsFloat {
|
} else if inputProp.SampleSize == 2 && !inputProp.IsFloat {
|
||||||
@@ -119,26 +130,44 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
|
|||||||
return nil, errUnsupportedFormat
|
return nil, errUnsupportedFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||||
onRecvChunk := func(_, chunk []byte, framecount uint32) {
|
onRecvChunk := func(_, chunk []byte, framecount uint32) {
|
||||||
m.chunkChan <- chunk
|
select {
|
||||||
|
case <-cancelCtx.Done():
|
||||||
|
case m.chunkChan <- chunk:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
callbacks.Data = onRecvChunk
|
callbacks.Data = onRecvChunk
|
||||||
|
|
||||||
device, err := malgo.InitDevice(ctx.Context, config, callbacks)
|
device, err := malgo.InitDevice(ctx.Context, config, callbacks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
cancel()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = device.Start()
|
err = device.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
cancel()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var closeDeviceOnce sync.Once
|
||||||
|
m.deviceCloseFunc = func() {
|
||||||
|
closeDeviceOnce.Do(func() {
|
||||||
|
cancel() // Unblock onRecvChunk
|
||||||
|
device.Uninit()
|
||||||
|
|
||||||
|
if m.chunkChan != nil {
|
||||||
|
close(m.chunkChan)
|
||||||
|
m.chunkChan = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var reader audio.Reader = audio.ReaderFunc(func() (wave.Audio, func(), error) {
|
var reader audio.Reader = audio.ReaderFunc(func() (wave.Audio, func(), error) {
|
||||||
chunk, ok := <-m.chunkChan
|
chunk, ok := <-m.chunkChan
|
||||||
if !ok {
|
if !ok {
|
||||||
device.Stop()
|
m.deviceCloseFunc()
|
||||||
device.Uninit()
|
|
||||||
return nil, func() {}, io.EOF
|
return nil, func() {}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +184,6 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
|
|||||||
return decodedChunk, func() {}, err
|
return decodedChunk, func() {}, err
|
||||||
})
|
})
|
||||||
|
|
||||||
// FIXME: The current audio detection and audio encoder can only work with a static latency. Since the latency from the driver
|
|
||||||
// can fluctuate, we need to stabilize it. Maybe there's a better way for doing this?
|
|
||||||
reader = audio.NewBuffer(int(inputProp.Latency.Seconds() * float64(inputProp.SampleRate)))(reader)
|
|
||||||
return reader, nil
|
return reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,16 +197,13 @@ func (m *microphone) Properties() []prop.Media {
|
|||||||
isBigEndian = true
|
isBigEndian = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for ch := m.MinChannels; ch <= m.MaxChannels; ch++ {
|
for _, format := range m.Formats {
|
||||||
// FIXME: Currently support 48kHz only. We need to implement a resampler first.
|
// FIXME: Currently support 48kHz only. We need to implement a resampler first.
|
||||||
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
|
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
|
||||||
sampleRate := 48000
|
sampleRate := 48000
|
||||||
for i := 0; i < int(m.FormatCount); i++ {
|
|
||||||
format := m.Formats[i]
|
|
||||||
|
|
||||||
supportedProp := prop.Media{
|
supportedProp := prop.Media{
|
||||||
Audio: prop.Audio{
|
Audio: prop.Audio{
|
||||||
ChannelCount: int(ch),
|
ChannelCount: int(format.Channels),
|
||||||
SampleRate: int(sampleRate),
|
SampleRate: int(sampleRate),
|
||||||
IsBigEndian: isBigEndian,
|
IsBigEndian: isBigEndian,
|
||||||
// miniaudio only supports interleaved at the moment
|
// miniaudio only supports interleaved at the moment
|
||||||
@@ -190,17 +213,23 @@ func (m *microphone) Properties() []prop.Media {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch malgo.FormatType(format) {
|
supportedFormat := true
|
||||||
|
switch malgo.FormatType(format.Format) {
|
||||||
case malgo.FormatF32:
|
case malgo.FormatF32:
|
||||||
supportedProp.SampleSize = 4
|
supportedProp.SampleSize = 4
|
||||||
supportedProp.IsFloat = true
|
supportedProp.IsFloat = true
|
||||||
case malgo.FormatS16:
|
case malgo.FormatS16:
|
||||||
supportedProp.SampleSize = 2
|
supportedProp.SampleSize = 2
|
||||||
supportedProp.IsFloat = false
|
supportedProp.IsFloat = false
|
||||||
|
default:
|
||||||
|
supportedFormat = false
|
||||||
}
|
}
|
||||||
|
|
||||||
supportedProps = append(supportedProps, supportedProp)
|
if !supportedFormat {
|
||||||
|
logger.Warnf("format '%s' not supported", format.Format)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
supportedProps = append(supportedProps, supportedProp)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
return supportedProps
|
return supportedProps
|
||||||
|
@@ -20,6 +20,11 @@ type screen struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize finds and registers active displays. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
activeDisplays := screenshot.NumActiveDisplays()
|
activeDisplays := screenshot.NumActiveDisplays()
|
||||||
for i := 0; i < activeDisplays; i++ {
|
for i := 0; i < activeDisplays; i++ {
|
||||||
priority := driver.PriorityNormal
|
priority := driver.PriorityNormal
|
||||||
|
@@ -22,6 +22,11 @@ func deviceID(num int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize finds and registers active displays. This is part of an experimental API.
|
||||||
|
func Initialize() {
|
||||||
dp, err := openDisplay()
|
dp, err := openDisplay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No x11 display available.
|
// No x11 display available.
|
||||||
|
@@ -2,6 +2,7 @@ package screen
|
|||||||
|
|
||||||
// #cgo pkg-config: x11 xext
|
// #cgo pkg-config: x11 xext
|
||||||
// #include <stdint.h>
|
// #include <stdint.h>
|
||||||
|
// #include <string.h>
|
||||||
// #include <sys/shm.h>
|
// #include <sys/shm.h>
|
||||||
// #include <X11/Xlib.h>
|
// #include <X11/Xlib.h>
|
||||||
// #define XUTIL_DEFINE_FUNCTIONS
|
// #define XUTIL_DEFINE_FUNCTIONS
|
||||||
@@ -125,6 +126,14 @@ func (c colorFunc) RGBA() (r, g, b, a uint32) {
|
|||||||
|
|
||||||
func (s *shmImage) At(x, y int) color.Color {
|
func (s *shmImage) At(x, y int) color.Color {
|
||||||
switch s.pixFmt {
|
switch s.pixFmt {
|
||||||
|
case pixFmtRGB24:
|
||||||
|
addr := (x + y*int(s.img.width)) * 4
|
||||||
|
r := uint32(s.b[addr]) * 0x100
|
||||||
|
g := uint32(s.b[addr+1]) * 0x100
|
||||||
|
b := uint32(s.b[addr+2]) * 0x100
|
||||||
|
return colorFunc(func() (_, _, _, _ uint32) {
|
||||||
|
return r, g, b, 0xFFFF
|
||||||
|
})
|
||||||
case pixFmtBGR24:
|
case pixFmtBGR24:
|
||||||
addr := (x + y*int(s.img.width)) * 4
|
addr := (x + y*int(s.img.width)) * 4
|
||||||
b := uint32(s.b[addr]) * 0x100
|
b := uint32(s.b[addr]) * 0x100
|
||||||
@@ -133,6 +142,15 @@ func (s *shmImage) At(x, y int) color.Color {
|
|||||||
return colorFunc(func() (_, _, _, _ uint32) {
|
return colorFunc(func() (_, _, _, _ uint32) {
|
||||||
return r, g, b, 0xFFFF
|
return r, g, b, 0xFFFF
|
||||||
})
|
})
|
||||||
|
case pixFmtRGB16:
|
||||||
|
addr := (x + y*int(s.img.width)) * 2
|
||||||
|
b1, b2 := s.b[addr], s.b[addr+1]
|
||||||
|
r := uint32(b1>>3) * 0x100
|
||||||
|
g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100
|
||||||
|
b := uint32(b2&0x1F) * 0x100
|
||||||
|
return colorFunc(func() (_, _, _, _ uint32) {
|
||||||
|
return r, g, b, 0xFFFF
|
||||||
|
})
|
||||||
case pixFmtBGR16:
|
case pixFmtBGR16:
|
||||||
addr := (x + y*int(s.img.width)) * 2
|
addr := (x + y*int(s.img.width)) * 2
|
||||||
b1, b2 := s.b[addr], s.b[addr+1]
|
b1, b2 := s.b[addr], s.b[addr+1]
|
||||||
@@ -149,12 +167,25 @@ func (s *shmImage) At(x, y int) color.Color {
|
|||||||
|
|
||||||
func (s *shmImage) RGBAAt(x, y int) color.RGBA {
|
func (s *shmImage) RGBAAt(x, y int) color.RGBA {
|
||||||
switch s.pixFmt {
|
switch s.pixFmt {
|
||||||
|
case pixFmtRGB24:
|
||||||
|
addr := (x + y*int(s.img.width)) * 4
|
||||||
|
r := s.b[addr]
|
||||||
|
g := s.b[addr+1]
|
||||||
|
b := s.b[addr+2]
|
||||||
|
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||||
case pixFmtBGR24:
|
case pixFmtBGR24:
|
||||||
addr := (x + y*int(s.img.width)) * 4
|
addr := (x + y*int(s.img.width)) * 4
|
||||||
b := s.b[addr]
|
b := s.b[addr]
|
||||||
g := s.b[addr+1]
|
g := s.b[addr+1]
|
||||||
r := s.b[addr+2]
|
r := s.b[addr+2]
|
||||||
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||||
|
case pixFmtRGB16:
|
||||||
|
addr := (x + y*int(s.img.width)) * 2
|
||||||
|
b1, b2 := s.b[addr], s.b[addr+1]
|
||||||
|
r := b1 >> 3
|
||||||
|
g := (b1&0x7)<<3 | (b2&0xE0)>>5
|
||||||
|
b := b2 & 0x1F
|
||||||
|
return color.RGBA{R: r, G: g, B: b, A: 0xFF}
|
||||||
case pixFmtBGR16:
|
case pixFmtBGR16:
|
||||||
addr := (x + y*int(s.img.width)) * 2
|
addr := (x + y*int(s.img.width)) * 2
|
||||||
b1, b2 := s.b[addr], s.b[addr+1]
|
b1, b2 := s.b[addr], s.b[addr+1]
|
||||||
@@ -178,11 +209,17 @@ func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA {
|
|||||||
dst.Pix = dst.Pix[:l]
|
dst.Pix = dst.Pix[:l]
|
||||||
}
|
}
|
||||||
switch s.pixFmt {
|
switch s.pixFmt {
|
||||||
|
case pixFmtRGB24:
|
||||||
|
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
|
||||||
|
return dst
|
||||||
case pixFmtBGR24:
|
case pixFmtBGR24:
|
||||||
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
|
C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
|
||||||
|
return dst
|
||||||
|
case pixFmtRGB16:
|
||||||
|
C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix)))
|
||||||
return dst
|
return dst
|
||||||
case pixFmtBGR16:
|
case pixFmtBGR16:
|
||||||
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
|
C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix)))
|
||||||
return dst
|
return dst
|
||||||
default:
|
default:
|
||||||
panic("unsupported pixel format")
|
panic("unsupported pixel format")
|
||||||
@@ -199,8 +236,12 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
|
|||||||
s := &shmImage{dp: dp}
|
s := &shmImage{dp: dp}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case v.red_mask == 0xFF && v.green_mask == 0xFF00 && v.blue_mask == 0xFF0000:
|
||||||
|
s.pixFmt = pixFmtRGB24
|
||||||
case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF:
|
case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF:
|
||||||
s.pixFmt = pixFmtBGR24
|
s.pixFmt = pixFmtBGR24
|
||||||
|
case v.red_mask == 0x1F && v.green_mask == 0x7E0 && v.blue_mask == 0xF800:
|
||||||
|
s.pixFmt = pixFmtRGB16
|
||||||
case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F:
|
case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F:
|
||||||
s.pixFmt = pixFmtBGR16
|
s.pixFmt = pixFmtBGR16
|
||||||
default:
|
default:
|
||||||
@@ -209,7 +250,7 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
|
|||||||
return nil, errors.New("unsupported pixel format")
|
return nil, errors.New("unsupported pixel format")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.ulong(w*h*4+8), C.IPC_CREAT|0600)
|
s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.size_t(w*h*4+8), C.IPC_CREAT|0600)
|
||||||
if s.shm.shmid == -1 {
|
if s.shm.shmid == -1 {
|
||||||
return nil, errors.New("failed to get shared memory")
|
return nil, errors.New("failed to get shared memory")
|
||||||
}
|
}
|
||||||
@@ -279,5 +320,5 @@ func (r *reader) Close() {
|
|||||||
|
|
||||||
// cAlign64 is fot testing
|
// cAlign64 is fot testing
|
||||||
func cAlign64(ptr uintptr) uintptr {
|
func cAlign64(ptr uintptr) uintptr {
|
||||||
return uintptr(C.align64ForTest(C.ulong(uintptr(ptr))))
|
return uintptr(C.align64ForTest(C.size_t(uintptr(ptr))))
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user