mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 12:52:20 +08:00
Compare commits
25 Commits
fix/rtx_fe
...
renovate/g
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bc30e5d0b7 | ||
![]() |
cb394eb4c5 | ||
![]() |
e9f3dc20b6 | ||
![]() |
0710906fc7 | ||
![]() |
7fdafa9598 | ||
![]() |
5a19127623 | ||
![]() |
8ca6903676 | ||
![]() |
de517d790b | ||
![]() |
81cfc047d5 | ||
![]() |
1406108fb2 | ||
![]() |
a2a211857c | ||
![]() |
c0721738c4 | ||
![]() |
6047a32ea0 | ||
![]() |
60bf158757 | ||
![]() |
c4fd28c7df | ||
![]() |
c79e16706b | ||
![]() |
89420ae84d | ||
![]() |
4db71e5b52 | ||
![]() |
a45a5e50cd | ||
![]() |
d90220699e | ||
![]() |
71deb52047 | ||
![]() |
84ccb15157 | ||
![]() |
ec6a4b6925 | ||
![]() |
551fb6afd8 | ||
![]() |
20e8c50735 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
- name: Installing go-licenses
|
- name: Installing go-licenses
|
||||||
|
@@ -5,7 +5,7 @@ go 1.21
|
|||||||
require (
|
require (
|
||||||
github.com/esimov/pigo v1.4.6
|
github.com/esimov/pigo v1.4.6
|
||||||
github.com/pion/mediadevices v0.0.0
|
github.com/pion/mediadevices v0.0.0
|
||||||
github.com/pion/webrtc/v4 v4.0.14
|
github.com/pion/webrtc/v4 v4.1.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -13,20 +13,20 @@ require (
|
|||||||
github.com/gen2brain/malgo v0.11.23 // indirect
|
github.com/gen2brain/malgo v0.11.23 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/pion/datachannel v1.5.10 // indirect
|
github.com/pion/datachannel v1.5.10 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.4 // indirect
|
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||||
github.com/pion/ice/v4 v4.0.8 // indirect
|
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||||
github.com/pion/interceptor v0.1.37 // indirect
|
github.com/pion/interceptor v0.1.40 // indirect
|
||||||
github.com/pion/logging v0.2.3 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.15 // indirect
|
github.com/pion/rtcp v1.2.15 // indirect
|
||||||
github.com/pion/rtp v1.8.13 // indirect
|
github.com/pion/rtp v1.8.21 // indirect
|
||||||
github.com/pion/sctp v1.8.37 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.11 // indirect
|
github.com/pion/sdp/v3 v3.0.15 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
github.com/pion/srtp/v3 v3.0.7 // indirect
|
||||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
golang.org/x/image v0.23.0 // indirect
|
golang.org/x/image v0.23.0 // indirect
|
||||||
|
@@ -13,40 +13,40 @@ 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||||
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
|
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
|
||||||
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
|
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
|
||||||
github.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY=
|
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||||
github.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
|
||||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
|
||||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||||
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.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg=
|
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
|
||||||
github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
|
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
|
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
|
||||||
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
|
||||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
|
||||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||||
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
|
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
|
||||||
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
|
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
|
||||||
github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg=
|
github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
|
||||||
github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk=
|
github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
|
||||||
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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
24
go.mod
24
go.mod
@@ -6,13 +6,13 @@ require (
|
|||||||
github.com/blackjack/webcam v0.6.1
|
github.com/blackjack/webcam v0.6.1
|
||||||
github.com/gen2brain/malgo v0.11.23
|
github.com/gen2brain/malgo v0.11.23
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c
|
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
|
||||||
github.com/pion/interceptor v0.1.37
|
github.com/pion/interceptor v0.1.40
|
||||||
github.com/pion/logging v0.2.3
|
github.com/pion/logging v0.2.4
|
||||||
github.com/pion/rtcp v1.2.15
|
github.com/pion/rtcp v1.2.15
|
||||||
github.com/pion/rtp v1.8.13
|
github.com/pion/rtp v1.8.21
|
||||||
github.com/pion/webrtc/v4 v4.0.14
|
github.com/pion/webrtc/v4 v4.1.4
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
golang.org/x/image v0.23.0
|
golang.org/x/image v0.23.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,16 +23,16 @@ require (
|
|||||||
github.com/jezek/xgb v1.1.1 // indirect
|
github.com/jezek/xgb v1.1.1 // indirect
|
||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||||
github.com/pion/datachannel v1.5.10 // indirect
|
github.com/pion/datachannel v1.5.10 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.4 // indirect
|
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||||
github.com/pion/ice/v4 v4.0.8 // indirect
|
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/sctp v1.8.37 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.11 // indirect
|
github.com/pion/sdp/v3 v3.0.15 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
github.com/pion/srtp/v3 v3.0.7 // indirect
|
||||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
48
go.sum
48
go.sum
@@ -12,8 +12,8 @@ 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
||||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c h1:1IlzDla/ZATV/FsRn1ETf7ir91PHS2mrd4VMunEtd9k=
|
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
|
||||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
|
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
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/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=
|
||||||
@@ -24,40 +24,40 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+
|
|||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||||
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||||
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
|
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
|
||||||
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
|
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
|
||||||
github.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY=
|
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||||
github.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
|
||||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
|
||||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||||
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.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg=
|
github.com/pion/rtp v1.8.21 h1:3yrOwmZFyUpcIosNcWRpQaU+UXIJ6yxLuJ8Bx0mw37Y=
|
||||||
github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
|
github.com/pion/rtp v1.8.21/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
|
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
|
||||||
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
|
||||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
|
||||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||||
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
|
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
|
||||||
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
|
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
|
||||||
github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg=
|
github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
|
||||||
github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk=
|
github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
|
||||||
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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
@@ -2,6 +2,7 @@ package mediadevices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
@@ -93,13 +94,7 @@ func TestMediaStreamFilters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range actual {
|
for _, a := range actual {
|
||||||
found := false
|
found := slices.Contains(expected, a)
|
||||||
for _, e := range expected {
|
|
||||||
if e == a {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatalf("%s: Expected to find %p in the query results", t.Name(), a)
|
t.Fatalf("%s: Expected to find %p in the query results", t.Name(), a)
|
||||||
|
48
pkg/codec/bitrate_tracker.go
Normal file
48
pkg/codec/bitrate_tracker.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BitrateTracker struct {
|
||||||
|
windowSize time.Duration
|
||||||
|
buffer []int
|
||||||
|
times []time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBitrateTracker(windowSize time.Duration) *BitrateTracker {
|
||||||
|
return &BitrateTracker{
|
||||||
|
windowSize: windowSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BitrateTracker) AddFrame(sizeBytes int, timestamp time.Time) {
|
||||||
|
bt.buffer = append(bt.buffer, sizeBytes)
|
||||||
|
bt.times = append(bt.times, timestamp)
|
||||||
|
|
||||||
|
// Remove old entries outside the window
|
||||||
|
cutoff := timestamp.Add(-bt.windowSize)
|
||||||
|
i := 0
|
||||||
|
for ; i < len(bt.times); i++ {
|
||||||
|
if bt.times[i].After(cutoff) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bt.buffer = bt.buffer[i:]
|
||||||
|
bt.times = bt.times[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BitrateTracker) GetBitrate() float64 {
|
||||||
|
if len(bt.times) < 2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
totalBytes := 0
|
||||||
|
for _, b := range bt.buffer {
|
||||||
|
totalBytes += b
|
||||||
|
}
|
||||||
|
duration := bt.times[len(bt.times)-1].Sub(bt.times[0]).Seconds()
|
||||||
|
if duration <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(totalBytes*8) / duration // bits per second
|
||||||
|
}
|
19
pkg/codec/bitrate_tracker_test.go
Normal file
19
pkg/codec/bitrate_tracker_test.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBitrateTracker(t *testing.T) {
|
||||||
|
packetSize := 1000
|
||||||
|
bt := NewBitrateTracker(time.Second)
|
||||||
|
bt.AddFrame(packetSize, time.Now())
|
||||||
|
bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*100))
|
||||||
|
bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*999))
|
||||||
|
eps := float64(packetSize*8) / 10
|
||||||
|
if got, want := bt.GetBitrate(), float64(packetSize*8)*3; math.Abs(got-want) > eps {
|
||||||
|
t.Fatalf("GetBitrate() = %v, want %v (|diff| <= %v)", got, want, eps)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package codec
|
package codec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/mediadevices/pkg/io/audio"
|
"github.com/pion/mediadevices/pkg/io/audio"
|
||||||
@@ -48,7 +50,7 @@ func NewRTPH265Codec(clockrate uint32) *RTPCodec {
|
|||||||
SDPFmtpLine: "",
|
SDPFmtpLine: "",
|
||||||
RTCPFeedback: nil,
|
RTCPFeedback: nil,
|
||||||
},
|
},
|
||||||
PayloadType: 125,
|
PayloadType: 116,
|
||||||
},
|
},
|
||||||
Payloader: &codecs.H265Payloader{},
|
Payloader: &codecs.H265Payloader{},
|
||||||
}
|
}
|
||||||
@@ -153,10 +155,19 @@ type ReadCloser interface {
|
|||||||
Controllable
|
Controllable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VideoDecoderBuilder interface {
|
||||||
|
BuildVideoDecoder(r io.Reader, p prop.Media) (VideoDecoder, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideoDecoder interface {
|
||||||
|
Read() (image.Image, func(), error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
// EncoderController is the interface allowing to control the encoder behaviour after it's initialisation.
|
// EncoderController is the interface allowing to control the encoder behaviour after it's initialisation.
|
||||||
// It will possibly have common control method in the future.
|
// It will possibly have common control method in the future.
|
||||||
// A controller can have optional methods represented by *Controller interfaces
|
// A controller can have optional methods represented by *Controller interfaces
|
||||||
type EncoderController interface{}
|
type EncoderController any
|
||||||
|
|
||||||
// Controllable is a interface representing a encoder which can be controlled
|
// Controllable is a interface representing a encoder which can be controlled
|
||||||
// after it's initialisation with an EncoderController
|
// after it's initialisation with an EncoderController
|
||||||
@@ -179,6 +190,12 @@ type BitRateController interface {
|
|||||||
SetBitRate(int) error
|
SetBitRate(int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QPController interface {
|
||||||
|
EncoderController
|
||||||
|
// DynamicQPControl adjusts the QP of the encoder based on the current and target bitrate
|
||||||
|
DynamicQPControl(currentBitrate int, targetBitrate int) error
|
||||||
|
}
|
||||||
|
|
||||||
// BaseParams represents an codec's encoding properties
|
// BaseParams represents an codec's encoding properties
|
||||||
type BaseParams struct {
|
type BaseParams struct {
|
||||||
// Target bitrate in bps.
|
// Target bitrate in bps.
|
||||||
|
@@ -54,6 +54,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -74,12 +75,19 @@ type encoder struct {
|
|||||||
frame []byte
|
frame []byte
|
||||||
deadline int
|
deadline int
|
||||||
requireKeyFrame bool
|
requireKeyFrame bool
|
||||||
|
targetBitrate int
|
||||||
isKeyFrame bool
|
isKeyFrame bool
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
kRateControlThreshold = 0.15
|
||||||
|
kMinQuantizer = 20
|
||||||
|
kMaxQuantizer = 63
|
||||||
|
)
|
||||||
|
|
||||||
// VP8Params is codec specific paramaters
|
// VP8Params is codec specific paramaters
|
||||||
type VP8Params struct {
|
type VP8Params struct {
|
||||||
Params
|
Params
|
||||||
@@ -200,14 +208,15 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
|
|||||||
}
|
}
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
return &encoder{
|
return &encoder{
|
||||||
r: video.ToI420(r),
|
r: video.ToI420(r),
|
||||||
codec: codec,
|
codec: codec,
|
||||||
raw: rawNoBuffer,
|
raw: rawNoBuffer,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
tStart: t0,
|
tStart: t0,
|
||||||
tLastFrame: t0,
|
tLastFrame: t0,
|
||||||
deadline: int(params.Deadline / time.Microsecond),
|
deadline: int(params.Deadline / time.Microsecond),
|
||||||
frame: make([]byte, 1024),
|
frame: make([]byte, 1024),
|
||||||
|
targetBitrate: params.BitRate,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +261,10 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ec := C.vpx_codec_enc_config_set(e.codec, e.cfg); ec != 0 {
|
||||||
|
return nil, func() {}, fmt.Errorf("vpx_codec_enc_config_set failed (%d)", ec)
|
||||||
|
}
|
||||||
|
|
||||||
duration := t.Sub(e.tLastFrame).Microseconds()
|
duration := t.Sub(e.tLastFrame).Microseconds()
|
||||||
// VPX doesn't allow 0 duration. If 0 is given, vpx_codec_encode will fail with VPX_CODEC_INVALID_PARAM.
|
// VPX doesn't allow 0 duration. If 0 is given, vpx_codec_encode will fail with VPX_CODEC_INVALID_PARAM.
|
||||||
// 0 duration is possible because mediadevices first gets the frame meta data by reading from the source,
|
// 0 duration is possible because mediadevices first gets the frame meta data by reading from the source,
|
||||||
@@ -261,6 +274,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
|
|||||||
if duration == 0 {
|
if duration == 0 {
|
||||||
duration = 1
|
duration = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetVpxBitrate := C.uint(float32(e.targetBitrate / 1000)) // convert to kilobits / second
|
||||||
|
if e.cfg.rc_target_bitrate != targetVpxBitrate && targetVpxBitrate >= 1 {
|
||||||
|
e.cfg.rc_target_bitrate = targetVpxBitrate
|
||||||
|
rc := C.vpx_codec_enc_config_set(e.codec, e.cfg)
|
||||||
|
if rc != C.VPX_CODEC_OK {
|
||||||
|
return nil, func() {}, fmt.Errorf("vpx_codec_enc_config_set failed (%d)", rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var flags int
|
var flags int
|
||||||
if e.requireKeyFrame {
|
if e.requireKeyFrame {
|
||||||
flags = flags | C.VPX_EFLAG_FORCE_KF
|
flags = flags | C.VPX_EFLAG_FORCE_KF
|
||||||
@@ -303,6 +326,31 @@ func (e *encoder) ForceKeyFrame() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *encoder) SetBitRate(bitrate int) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.targetBitrate = bitrate
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) DynamicQPControl(currentBitrate int, targetBitrate int) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
bitrateDiff := math.Abs(float64(currentBitrate - targetBitrate))
|
||||||
|
if bitrateDiff <= float64(currentBitrate)*kRateControlThreshold {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
currentMax := e.cfg.rc_max_quantizer
|
||||||
|
|
||||||
|
if targetBitrate < currentBitrate {
|
||||||
|
e.cfg.rc_max_quantizer = min(currentMax+1, kMaxQuantizer)
|
||||||
|
} else {
|
||||||
|
e.cfg.rc_max_quantizer = max(currentMax-1, kMinQuantizer)
|
||||||
|
}
|
||||||
|
e.cfg.rc_min_quantizer = e.cfg.rc_max_quantizer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *encoder) Controller() codec.EncoderController {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
155
pkg/codec/vpx/vpx_decoder.go
Normal file
155
pkg/codec/vpx/vpx_decoder.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package vpx
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: vpx
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vpx/vpx_decoder.h>
|
||||||
|
#include <vpx/vpx_codec.h>
|
||||||
|
#include <vpx/vpx_image.h>
|
||||||
|
#include <vpx/vp8dx.h>
|
||||||
|
|
||||||
|
vpx_codec_iface_t *ifaceVP8Decoder() {
|
||||||
|
return vpx_codec_vp8_dx();
|
||||||
|
}
|
||||||
|
vpx_codec_iface_t *ifaceVP9Decoder() {
|
||||||
|
return vpx_codec_vp9_dx();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocates a new decoder context
|
||||||
|
vpx_codec_ctx_t* newDecoderCtx() {
|
||||||
|
return (vpx_codec_ctx_t*)malloc(sizeof(vpx_codec_ctx_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes the decoder
|
||||||
|
vpx_codec_err_t decoderInit(vpx_codec_ctx_t* ctx, vpx_codec_iface_t* iface) {
|
||||||
|
return vpx_codec_dec_init_ver(ctx, iface, NULL, 0, VPX_DECODER_ABI_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes an encoded frame
|
||||||
|
vpx_codec_err_t decodeFrame(vpx_codec_ctx_t* ctx, const uint8_t* data, unsigned int data_sz) {
|
||||||
|
return vpx_codec_decode(ctx, data, data_sz, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an iterator
|
||||||
|
vpx_codec_iter_t* newIter() {
|
||||||
|
return (vpx_codec_iter_t*)malloc(sizeof(vpx_codec_iter_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the next decoded frame
|
||||||
|
vpx_image_t* getFrame(vpx_codec_ctx_t* ctx, vpx_codec_iter_t* iter) {
|
||||||
|
return vpx_codec_get_frame(ctx, iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frees a decoded frane
|
||||||
|
void freeFrame(vpx_image_t* f) {
|
||||||
|
vpx_img_free(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frees a decoder context
|
||||||
|
void freeDecoderCtx(vpx_codec_ctx_t* ctx) {
|
||||||
|
vpx_codec_destroy(ctx);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
codec *C.vpx_codec_ctx_t
|
||||||
|
raw *C.vpx_image_t
|
||||||
|
cfg *C.vpx_codec_dec_cfg_t
|
||||||
|
iter C.vpx_codec_iter_t
|
||||||
|
frameIndex int
|
||||||
|
tStart time.Time
|
||||||
|
tLastFrame time.Time
|
||||||
|
reader io.Reader
|
||||||
|
buf []byte
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildVideoDecoder(r io.Reader, property prop.Media) (codec.VideoDecoder, error) {
|
||||||
|
return NewDecoder(r, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDecoder(r io.Reader, p prop.Media) (codec.VideoDecoder, error) {
|
||||||
|
cfg := &C.vpx_codec_dec_cfg_t{}
|
||||||
|
cfg.threads = 1
|
||||||
|
cfg.w = C.uint(p.Width)
|
||||||
|
cfg.h = C.uint(p.Height)
|
||||||
|
|
||||||
|
codec := C.newDecoderCtx()
|
||||||
|
if C.decoderInit(codec, C.ifaceVP8Decoder()) != C.VPX_CODEC_OK {
|
||||||
|
return nil, fmt.Errorf("vpx_codec_dec_init failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &decoder{
|
||||||
|
codec: codec,
|
||||||
|
cfg: cfg,
|
||||||
|
iter: nil, // initialize to NULL to start iteration
|
||||||
|
reader: r,
|
||||||
|
buf: make([]byte, 1024*1024),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Read() (image.Image, func(), error) {
|
||||||
|
var input *C.vpx_image_t
|
||||||
|
for {
|
||||||
|
input = C.getFrame(d.codec, &d.iter)
|
||||||
|
if input != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.iter = nil
|
||||||
|
// Read if there are no remained frames in the decoder
|
||||||
|
n, err := d.reader.Read(d.buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
status := C.decodeFrame(d.codec, (*C.uint8_t)(&d.buf[0]), C.uint(n))
|
||||||
|
if status != C.VPX_CODEC_OK {
|
||||||
|
return nil, nil, fmt.Errorf("decode failed: %v", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w := int(input.d_w)
|
||||||
|
h := int(input.d_h)
|
||||||
|
yStride := int(input.stride[0])
|
||||||
|
uStride := int(input.stride[1])
|
||||||
|
vStride := int(input.stride[2])
|
||||||
|
|
||||||
|
ySrc := unsafe.Slice((*byte)(unsafe.Pointer(input.planes[0])), yStride*h)
|
||||||
|
uSrc := unsafe.Slice((*byte)(unsafe.Pointer(input.planes[1])), uStride*h/2)
|
||||||
|
vSrc := unsafe.Slice((*byte)(unsafe.Pointer(input.planes[2])), vStride*h/2)
|
||||||
|
|
||||||
|
dst := image.NewYCbCr(image.Rect(0, 0, w, h), image.YCbCrSubsampleRatio420)
|
||||||
|
|
||||||
|
// copy luma
|
||||||
|
for r := 0; r < h; r++ {
|
||||||
|
copy(dst.Y[r*dst.YStride:r*dst.YStride+w], ySrc[r*yStride:r*yStride+w])
|
||||||
|
}
|
||||||
|
// copy chroma
|
||||||
|
for r := 0; r < h/2; r++ {
|
||||||
|
copy(dst.Cb[r*dst.CStride:r*dst.CStride+w/2], uSrc[r*uStride:r*uStride+w/2])
|
||||||
|
copy(dst.Cr[r*dst.CStride:r*dst.CStride+w/2], vSrc[r*vStride:r*vStride+w/2])
|
||||||
|
}
|
||||||
|
C.freeFrame(input)
|
||||||
|
return dst, func() {}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Close() error {
|
||||||
|
C.freeDecoderCtx(d.codec)
|
||||||
|
d.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +16,7 @@ import (
|
|||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
"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/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncoder(t *testing.T) {
|
func TestEncoder(t *testing.T) {
|
||||||
@@ -232,9 +236,76 @@ func TestRequestKeyFrame(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
func TestSetBitrate(t *testing.T) {
|
||||||
t.SkipNow() // TODO: Implement bit rate control
|
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()
|
||||||
|
err = r.Controller().(codec.BitRateController).SetBitRate(1000) // 1000 bit/second is ridiculously low, but this is a testcase.
|
||||||
|
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 TestShouldImplementBitRateControl(t *testing.T) {
|
||||||
e := &encoder{}
|
e := &encoder{}
|
||||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||||
t.Error()
|
t.Error()
|
||||||
@@ -293,3 +364,155 @@ func TestEncoderFrameMonotonic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVP8DynamicQPControl(t *testing.T) {
|
||||||
|
t.Run("VP8", func(t *testing.T) {
|
||||||
|
p, err := NewVP8Params()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p.LagInFrames = 0 // Disable frame lag buffering for real-time encoding
|
||||||
|
p.RateControlEndUsage = RateControlCBR
|
||||||
|
totalFrames := 100
|
||||||
|
frameRate := 10
|
||||||
|
initialWidth, initialHeight := 800, 600
|
||||||
|
var cnt uint32
|
||||||
|
|
||||||
|
r, err := p.BuildVideoEncoder(
|
||||||
|
video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
i := atomic.AddUint32(&cnt, 1)
|
||||||
|
if i == uint32(totalFrames+1) {
|
||||||
|
return nil, nil, io.EOF
|
||||||
|
}
|
||||||
|
img := image.NewYCbCr(image.Rect(0, 0, initialWidth, initialHeight), image.YCbCrSubsampleRatio420)
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
for i := range img.Y {
|
||||||
|
img.Y[i] = uint8(r.Intn(256))
|
||||||
|
}
|
||||||
|
for i := range img.Cb {
|
||||||
|
img.Cb[i] = uint8(r.Intn(256))
|
||||||
|
}
|
||||||
|
for i := range img.Cr {
|
||||||
|
img.Cr[i] = uint8(r.Intn(256))
|
||||||
|
}
|
||||||
|
return img, func() {}, nil
|
||||||
|
}),
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: initialWidth,
|
||||||
|
Height: initialHeight,
|
||||||
|
FrameRate: float32(frameRate),
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
initialBitrate := 100
|
||||||
|
currentBitrate := initialBitrate
|
||||||
|
targetBitrate := 300
|
||||||
|
for i := 0; i < totalFrames; i++ {
|
||||||
|
r.Controller().(codec.KeyFrameController).ForceKeyFrame()
|
||||||
|
r.Controller().(codec.QPController).DynamicQPControl(currentBitrate, targetBitrate)
|
||||||
|
data, rel, err := r.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rel()
|
||||||
|
encodedSize := len(data)
|
||||||
|
currentBitrate = encodedSize * 8 / 1000 / frameRate
|
||||||
|
}
|
||||||
|
assert.Less(t, math.Abs(float64(targetBitrate-currentBitrate)), math.Abs(float64(initialBitrate-currentBitrate)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVP8EncodeDecode(t *testing.T) {
|
||||||
|
t.Run("VP8", func(t *testing.T) {
|
||||||
|
initialWidth, initialHeight := 800, 600
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
decoder, err := BuildVideoDecoder(reader, prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: initialWidth,
|
||||||
|
Height: initialHeight,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating VP8 decoder: %v", err)
|
||||||
|
}
|
||||||
|
defer decoder.Close()
|
||||||
|
|
||||||
|
// [... encoder setup code ...]
|
||||||
|
p, err := NewVP8Params()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p.LagInFrames = 0 // Disable frame lag buffering for real-time encoding
|
||||||
|
p.RateControlEndUsage = RateControlCBR
|
||||||
|
totalFrames := 10
|
||||||
|
var cnt uint32
|
||||||
|
r, err := p.BuildVideoEncoder(
|
||||||
|
video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
i := atomic.AddUint32(&cnt, 1)
|
||||||
|
if i == uint32(totalFrames+1) {
|
||||||
|
return nil, nil, io.EOF
|
||||||
|
}
|
||||||
|
img := image.NewYCbCr(image.Rect(0, 0, initialWidth, initialHeight), image.YCbCrSubsampleRatio420)
|
||||||
|
return img, func() {}, nil
|
||||||
|
}),
|
||||||
|
prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: initialWidth,
|
||||||
|
Height: initialHeight,
|
||||||
|
FrameRate: 30,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
img, rel, err := decoder.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decoder read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, initialWidth, img.Bounds().Dx())
|
||||||
|
assert.Equal(t, initialHeight, img.Bounds().Dy())
|
||||||
|
rel()
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// --- feed encoded frames to writer
|
||||||
|
for {
|
||||||
|
data, rel, err := r.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encoder error: %v", err)
|
||||||
|
}
|
||||||
|
_, werr := writer.Write(data)
|
||||||
|
rel()
|
||||||
|
if werr != nil {
|
||||||
|
t.Fatalf("writer error: %v", werr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.Close()
|
||||||
|
wg.Wait()
|
||||||
|
assert.Equal(t, totalFrames, counter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#define ERR_ALLOC_PICTURE -3
|
#define ERR_ALLOC_PICTURE -3
|
||||||
#define ERR_OPEN_ENGINE -4
|
#define ERR_OPEN_ENGINE -4
|
||||||
#define ERR_ENCODE -5
|
#define ERR_ENCODE -5
|
||||||
|
#define ERR_BITRATE_RECONFIG -6
|
||||||
|
|
||||||
typedef struct Slice {
|
typedef struct Slice {
|
||||||
unsigned char *data;
|
unsigned char *data;
|
||||||
@@ -78,6 +79,22 @@ fail:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RC_MARGIN 10000 /* 1kilobits / second*/
|
||||||
|
static int apply_target_bitrate(Encoder *e, int target_bitrate) {
|
||||||
|
int target_encoder_bitrate = (int)target_bitrate / 1000;
|
||||||
|
if (e->param.rc.i_bitrate == target_encoder_bitrate || target_encoder_bitrate <= 1) {
|
||||||
|
return 0; // if no change to bitrate or target bitrate is too small, we return no error (0)
|
||||||
|
}
|
||||||
|
|
||||||
|
e->param.rc.i_bitrate = target_encoder_bitrate;
|
||||||
|
e->param.rc.f_rate_tolerance = 0.1;
|
||||||
|
e->param.rc.i_vbv_max_bitrate = target_encoder_bitrate + RC_MARGIN / 2;
|
||||||
|
e->param.rc.i_vbv_buffer_size = e->param.rc.i_vbv_max_bitrate;
|
||||||
|
e->param.rc.f_vbv_buffer_init = 0.6;
|
||||||
|
int success = x264_encoder_reconfig(e->h, &e->param);
|
||||||
|
return success; // 0 on success or negative on error
|
||||||
|
}
|
||||||
|
|
||||||
Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
|
Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
|
||||||
x264_nal_t *nal;
|
x264_nal_t *nal;
|
||||||
int i_nal;
|
int i_nal;
|
||||||
|
@@ -39,6 +39,8 @@ func (e cerror) Error() string {
|
|||||||
return errOpenEngine.Error()
|
return errOpenEngine.Error()
|
||||||
case C.ERR_ENCODE:
|
case C.ERR_ENCODE:
|
||||||
return errEncode.Error()
|
return errEncode.Error()
|
||||||
|
case C.ERR_BITRATE_RECONFIG:
|
||||||
|
return errSetBitrate.Error()
|
||||||
default:
|
default:
|
||||||
return "unknown error"
|
return "unknown error"
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,7 @@ var (
|
|||||||
errAllocPicture = fmt.Errorf("failed to alloc picture")
|
errAllocPicture = fmt.Errorf("failed to alloc picture")
|
||||||
errOpenEngine = fmt.Errorf("failed to open x264")
|
errOpenEngine = fmt.Errorf("failed to open x264")
|
||||||
errEncode = fmt.Errorf("failed to encode")
|
errEncode = fmt.Errorf("failed to encode")
|
||||||
|
errSetBitrate = fmt.Errorf("failed to change x264 encoder bitrate")
|
||||||
)
|
)
|
||||||
|
|
||||||
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
|
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
|
||||||
@@ -133,6 +136,16 @@ func (e *encoder) ForceKeyFrame() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *encoder) SetBitRate(bitrate int) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
errNum := C.apply_target_bitrate(e.engine, C.int(bitrate))
|
||||||
|
if err := errFromC(errNum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *encoder) Controller() codec.EncoderController {
|
func (e *encoder) Controller() codec.EncoderController {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,34 @@ import (
|
|||||||
"github.com/pion/mediadevices/pkg/codec"
|
"github.com/pion/mediadevices/pkg/codec"
|
||||||
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
"github.com/pion/mediadevices/pkg/io/video"
|
||||||
"github.com/pion/mediadevices/pkg/prop"
|
"github.com/pion/mediadevices/pkg/prop"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getTestVideoEncoder() (codec.ReadCloser, error) {
|
||||||
|
p, err := NewParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.BitRate = 200000
|
||||||
|
enc, err := p.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
|
||||||
|
return image.NewYCbCr(
|
||||||
|
image.Rect(0, 0, 256, 144),
|
||||||
|
image.YCbCrSubsampleRatio420,
|
||||||
|
), nil, nil
|
||||||
|
}), prop.Media{
|
||||||
|
Video: prop.Video{
|
||||||
|
Width: 256,
|
||||||
|
Height: 144,
|
||||||
|
FrameFormat: frame.FormatI420,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return enc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncoder(t *testing.T) {
|
func TestEncoder(t *testing.T) {
|
||||||
t.Run("SimpleRead", func(t *testing.T) {
|
t.Run("SimpleRead", func(t *testing.T) {
|
||||||
p, err := NewParams()
|
p, err := NewParams()
|
||||||
@@ -69,19 +94,53 @@ func TestEncoder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
func TestShouldImplementKeyFrameControl(t *testing.T) {
|
||||||
t.SkipNow() // TODO: Implement key frame control
|
|
||||||
|
|
||||||
e := &encoder{}
|
e := &encoder{}
|
||||||
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldImplementBitRateControl(t *testing.T) {
|
func TestNoErrorOnForceKeyFrame(t *testing.T) {
|
||||||
t.SkipNow() // TODO: Implement bit rate control
|
enc, err := getTestVideoEncoder()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
kfc, ok := enc.Controller().(codec.KeyFrameController)
|
||||||
|
if !ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if err := kfc.ForceKeyFrame(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, rel, err := enc.Read() // try to read the encoded frame
|
||||||
|
rel()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldImplementBitRateControl(t *testing.T) {
|
||||||
e := &encoder{}
|
e := &encoder{}
|
||||||
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
if _, ok := e.Controller().(codec.BitRateController); !ok {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoErrorOnSetBitRate(t *testing.T) {
|
||||||
|
enc, err := getTestVideoEncoder()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
brc, ok := enc.Controller().(codec.BitRateController)
|
||||||
|
if !ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if err := brc.SetBitRate(1000); err != nil { // 1000 bit/second is ridiculously low, but this is a testcase.
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, rel, err := enc.Read() // try to read the encoded frame
|
||||||
|
rel()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
// Package vnc implements a VNC client.
|
// Package vnc implements a VNC client.
|
||||||
//
|
//
|
||||||
// References:
|
// References:
|
||||||
// [PROTOCOL]: http://tools.ietf.org/html/rfc6143
|
//
|
||||||
|
// [PROTOCOL]: http://tools.ietf.org/html/rfc6143
|
||||||
package vnc
|
package vnc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -96,7 +97,7 @@ func (c *ClientConn) CutText(text string) error {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
// This is the fixed size data we'll send
|
// This is the fixed size data we'll send
|
||||||
fixedData := []interface{}{
|
fixedData := []any{
|
||||||
uint8(6),
|
uint8(6),
|
||||||
uint8(0),
|
uint8(0),
|
||||||
uint8(0),
|
uint8(0),
|
||||||
@@ -141,7 +142,7 @@ func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, hei
|
|||||||
incrementalByte = 1
|
incrementalByte = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
uint8(3),
|
uint8(3),
|
||||||
incrementalByte,
|
incrementalByte,
|
||||||
x, y, width, height,
|
x, y, width, height,
|
||||||
@@ -172,7 +173,7 @@ func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
|
|||||||
downFlag = 1
|
downFlag = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
uint8(4),
|
uint8(4),
|
||||||
downFlag,
|
downFlag,
|
||||||
uint8(0),
|
uint8(0),
|
||||||
@@ -199,7 +200,7 @@ func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
|
|||||||
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
|
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
uint8(5),
|
uint8(5),
|
||||||
uint8(mask),
|
uint8(mask),
|
||||||
x,
|
x,
|
||||||
@@ -225,7 +226,7 @@ func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
|
|||||||
//
|
//
|
||||||
// See RFC 6143 Section 7.5.2
|
// See RFC 6143 Section 7.5.2
|
||||||
func (c *ClientConn) SetEncodings(encs []Encoding) error {
|
func (c *ClientConn) SetEncodings(encs []Encoding) error {
|
||||||
data := make([]interface{}, 3+len(encs))
|
data := make([]any, 3+len(encs))
|
||||||
data[0] = uint8(2)
|
data[0] = uint8(2)
|
||||||
data[1] = uint8(0)
|
data[1] = uint8(0)
|
||||||
data[2] = uint16(len(encs))
|
data[2] = uint16(len(encs))
|
||||||
@@ -319,7 +320,7 @@ func (c *ClientConn) handshake() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Respond with the version we will support
|
// Respond with the version we will support
|
||||||
if maxMinor<8 {
|
if maxMinor < 8 {
|
||||||
if _, err = c.c.Write([]byte("RFB 003.003\n")); err != nil {
|
if _, err = c.c.Write([]byte("RFB 003.003\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -331,7 +332,7 @@ func (c *ClientConn) handshake() error {
|
|||||||
if numSecurityTypes == 0 {
|
if numSecurityTypes == 0 {
|
||||||
return fmt.Errorf("no security types: %s", c.readErrorReason())
|
return fmt.Errorf("no security types: %s", c.readErrorReason())
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil {
|
if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,7 @@ func (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (ServerMessage
|
|||||||
var encodingType int32
|
var encodingType int32
|
||||||
|
|
||||||
rect := &rects[i]
|
rect := &rects[i]
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
&rect.X,
|
&rect.X,
|
||||||
&rect.Y,
|
&rect.Y,
|
||||||
&rect.Width,
|
&rect.Width,
|
||||||
@@ -128,7 +128,7 @@ func (*SetColorMapEntriesMessage) Read(c *ClientConn, r io.Reader) (ServerMessag
|
|||||||
for i := uint16(0); i < numColors; i++ {
|
for i := uint16(0); i < numColors; i++ {
|
||||||
|
|
||||||
color := &result.Colors[i]
|
color := &result.Colors[i]
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
&color.R,
|
&color.R,
|
||||||
&color.G,
|
&color.G,
|
||||||
&color.B,
|
&color.B,
|
||||||
|
@@ -27,7 +27,7 @@ func NewBroadcaster(source Reader, config *BroadcasterConfig) *Broadcaster {
|
|||||||
coreConfig = config.Core
|
coreConfig = config.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcaster := io.NewBroadcaster(io.ReaderFunc(func() (interface{}, func(), error) {
|
broadcaster := io.NewBroadcaster(io.ReaderFunc(func() (any, func(), error) {
|
||||||
return source.Read()
|
return source.Read()
|
||||||
}), coreConfig)
|
}), coreConfig)
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ func NewBroadcaster(source Reader, config *BroadcasterConfig) *Broadcaster {
|
|||||||
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
||||||
// in the ring buffer.
|
// in the ring buffer.
|
||||||
func (broadcaster *Broadcaster) NewReader(copyChunk bool) Reader {
|
func (broadcaster *Broadcaster) NewReader(copyChunk bool) Reader {
|
||||||
copyFn := func(src interface{}) interface{} { return src }
|
copyFn := func(src any) any { return src }
|
||||||
|
|
||||||
if copyChunk {
|
if copyChunk {
|
||||||
buffer := wave.NewBuffer()
|
buffer := wave.NewBuffer()
|
||||||
copyFn = func(src interface{}) interface{} {
|
copyFn = func(src any) any {
|
||||||
realSrc, _ := src.(wave.Audio)
|
realSrc, _ := src.(wave.Audio)
|
||||||
buffer.StoreCopy(realSrc)
|
buffer.StoreCopy(realSrc)
|
||||||
return buffer.Load()
|
return buffer.Load()
|
||||||
@@ -60,7 +60,7 @@ func (broadcaster *Broadcaster) NewReader(copyChunk bool) Reader {
|
|||||||
|
|
||||||
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
||||||
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
||||||
return broadcaster.ioBroadcaster.ReplaceSource(io.ReaderFunc(func() (interface{}, func(), error) {
|
return broadcaster.ioBroadcaster.ReplaceSource(io.ReaderFunc(func() (any, func(), error) {
|
||||||
return source.Read()
|
return source.Read()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ const (
|
|||||||
var errEmptySource = fmt.Errorf("Source can't be nil")
|
var errEmptySource = fmt.Errorf("Source can't be nil")
|
||||||
|
|
||||||
type broadcasterData struct {
|
type broadcasterData struct {
|
||||||
data interface{}
|
data any
|
||||||
count uint32
|
count uint32
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -124,10 +124,10 @@ func NewBroadcaster(source Reader, config *BroadcasterConfig) *Broadcaster {
|
|||||||
// copyFn is used to copy the data from the source to individual readers. Broadcaster uses a small ring
|
// copyFn is used to copy the data from the source to individual readers. Broadcaster uses a small ring
|
||||||
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
||||||
// in the ring buffer.
|
// in the ring buffer.
|
||||||
func (broadcaster *Broadcaster) NewReader(copyFn func(interface{}) interface{}) Reader {
|
func (broadcaster *Broadcaster) NewReader(copyFn func(any) any) Reader {
|
||||||
currentCount := broadcaster.buffer.lastCount()
|
currentCount := broadcaster.buffer.lastCount()
|
||||||
|
|
||||||
return ReaderFunc(func() (data interface{}, release func(), err error) {
|
return ReaderFunc(func() (data any, release func(), err error) {
|
||||||
currentCount++
|
currentCount++
|
||||||
if push := broadcaster.buffer.acquire(currentCount); push != nil {
|
if push := broadcaster.buffer.acquire(currentCount); push != nil {
|
||||||
data, _, err = broadcaster.source.Load().(Reader).Read()
|
data, _, err = broadcaster.source.Load().(Reader).Read()
|
||||||
|
@@ -57,7 +57,7 @@ func TestBroadcast(t *testing.T) {
|
|||||||
frameCount := 0
|
frameCount := 0
|
||||||
frameSent := 0
|
frameSent := 0
|
||||||
lastSend := time.Now()
|
lastSend := time.Now()
|
||||||
src = ReaderFunc(func() (interface{}, func(), error) {
|
src = ReaderFunc(func() (any, func(), error) {
|
||||||
if pauseCond.src && frameSent == 30 {
|
if pauseCond.src && frameSent == 30 {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ func TestBroadcast(t *testing.T) {
|
|||||||
wg.Add(n)
|
wg.Add(n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
reader := broadcaster.NewReader(func(src interface{}) interface{} { return src })
|
reader := broadcaster.NewReader(func(src any) any { return src })
|
||||||
count := 0
|
count := 0
|
||||||
lastFrameCount := -1
|
lastFrameCount := -1
|
||||||
droppedFrames := 0
|
droppedFrames := 0
|
||||||
|
@@ -11,13 +11,13 @@ type Reader interface {
|
|||||||
// there will be new allocations during streaming, and old unused memory will become garbage. As a consequence,
|
// there will be new allocations during streaming, and old unused memory will become garbage. As a consequence,
|
||||||
// these garbage will put a lot of pressure to the garbage collector and makes it to run more often and finish
|
// these garbage will put a lot of pressure to the garbage collector and makes it to run more often and finish
|
||||||
// slower as the heap memory usage increases and more garbage to collect.
|
// slower as the heap memory usage increases and more garbage to collect.
|
||||||
Read() (data interface{}, release func(), err error)
|
Read() (data any, release func(), err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReaderFunc is a proxy type for Reader
|
// ReaderFunc is a proxy type for Reader
|
||||||
type ReaderFunc func() (data interface{}, release func(), err error)
|
type ReaderFunc func() (data any, release func(), err error)
|
||||||
|
|
||||||
func (f ReaderFunc) Read() (data interface{}, release func(), err error) {
|
func (f ReaderFunc) Read() (data any, release func(), err error) {
|
||||||
data, release, err = f()
|
data, release, err = f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ func NewBroadcaster(source Reader, config *BroadcasterConfig) *Broadcaster {
|
|||||||
coreConfig = config.Core
|
coreConfig = config.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcaster := io.NewBroadcaster(io.ReaderFunc(func() (interface{}, func(), error) {
|
broadcaster := io.NewBroadcaster(io.ReaderFunc(func() (any, func(), error) {
|
||||||
return source.Read()
|
return source.Read()
|
||||||
}), coreConfig)
|
}), coreConfig)
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ func NewBroadcaster(source Reader, config *BroadcasterConfig) *Broadcaster {
|
|||||||
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
||||||
// in the ring buffer.
|
// in the ring buffer.
|
||||||
func (broadcaster *Broadcaster) NewReader(copyFrame bool) Reader {
|
func (broadcaster *Broadcaster) NewReader(copyFrame bool) Reader {
|
||||||
copyFn := func(src interface{}) interface{} { return src }
|
copyFn := func(src any) any { return src }
|
||||||
|
|
||||||
if copyFrame {
|
if copyFrame {
|
||||||
buffer := NewFrameBuffer(0)
|
buffer := NewFrameBuffer(0)
|
||||||
copyFn = func(src interface{}) interface{} {
|
copyFn = func(src any) any {
|
||||||
realSrc, _ := src.(image.Image)
|
realSrc, _ := src.(image.Image)
|
||||||
buffer.StoreCopy(realSrc)
|
buffer.StoreCopy(realSrc)
|
||||||
return buffer.Load()
|
return buffer.Load()
|
||||||
@@ -60,7 +60,7 @@ func (broadcaster *Broadcaster) NewReader(copyFrame bool) Reader {
|
|||||||
|
|
||||||
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
||||||
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
||||||
return broadcaster.ioBroadcaster.ReplaceSource(io.ReaderFunc(func() (interface{}, func(), error) {
|
return broadcaster.ioBroadcaster.ReplaceSource(io.ReaderFunc(func() (any, func(), error) {
|
||||||
return source.Read()
|
return source.Read()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package prop
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -54,10 +55,8 @@ type DurationOneOf []time.Duration
|
|||||||
|
|
||||||
// Compare implements DurationConstraint.
|
// Compare implements DurationConstraint.
|
||||||
func (d DurationOneOf) Compare(a time.Duration) (float64, bool) {
|
func (d DurationOneOf) Compare(a time.Duration) (float64, bool) {
|
||||||
for _, ii := range d {
|
if slices.Contains(d, a) {
|
||||||
if ii == a {
|
return 0.0, true
|
||||||
return 0.0, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1.0, false
|
return 1.0, false
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package prop
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,10 +54,8 @@ type FloatOneOf []float32
|
|||||||
|
|
||||||
// Compare implements FloatConstraint.
|
// Compare implements FloatConstraint.
|
||||||
func (f FloatOneOf) Compare(a float32) (float64, bool) {
|
func (f FloatOneOf) Compare(a float32) (float64, bool) {
|
||||||
for _, ff := range f {
|
if slices.Contains(f, a) {
|
||||||
if ff == a {
|
return 0.0, true
|
||||||
return 0.0, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1.0, false
|
return 1.0, false
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package prop
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pion/mediadevices/pkg/frame"
|
"github.com/pion/mediadevices/pkg/frame"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,10 +57,8 @@ type FrameFormatOneOf []frame.Format
|
|||||||
|
|
||||||
// Compare implements FrameFormatConstraint.
|
// Compare implements FrameFormatConstraint.
|
||||||
func (f FrameFormatOneOf) Compare(a frame.Format) (float64, bool) {
|
func (f FrameFormatOneOf) Compare(a frame.Format) (float64, bool) {
|
||||||
for _, ff := range f {
|
if slices.Contains(f, a) {
|
||||||
if ff == a {
|
return 0.0, true
|
||||||
return 0.0, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1.0, false
|
return 1.0, false
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package prop
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,10 +54,8 @@ type IntOneOf []int
|
|||||||
|
|
||||||
// Compare implements IntConstraint.
|
// Compare implements IntConstraint.
|
||||||
func (i IntOneOf) Compare(a int) (float64, bool) {
|
func (i IntOneOf) Compare(a int) (float64, bool) {
|
||||||
for _, ii := range i {
|
if slices.Contains(i, a) {
|
||||||
if ii == a {
|
return 0.0, true
|
||||||
return 0.0, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1.0, false
|
return 1.0, false
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ func (m *Media) String() string {
|
|||||||
return prettifyStruct(m)
|
return prettifyStruct(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prettifyStruct(i interface{}) string {
|
func prettifyStruct(i any) string {
|
||||||
var rows []string
|
var rows []string
|
||||||
var addRows func(int, reflect.Value)
|
var addRows func(int, reflect.Value)
|
||||||
addRows = func(level int, obj reflect.Value) {
|
addRows = func(level int, obj reflect.Value) {
|
||||||
@@ -67,7 +67,7 @@ type setterFn func(fieldA, fieldB reflect.Value)
|
|||||||
|
|
||||||
// merge merges all the field values from o to p, except zero values. It's guaranteed that setterFn will be called
|
// merge merges all the field values from o to p, except zero values. It's guaranteed that setterFn will be called
|
||||||
// when fieldA and fieldB are not struct.
|
// when fieldA and fieldB are not struct.
|
||||||
func (p *Media) merge(o interface{}, set setterFn) {
|
func (p *Media) merge(o any, set setterFn) {
|
||||||
rp := reflect.ValueOf(p).Elem()
|
rp := reflect.ValueOf(p).Elem()
|
||||||
ro := reflect.ValueOf(o)
|
ro := reflect.ValueOf(o)
|
||||||
|
|
||||||
@@ -159,13 +159,13 @@ func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type comparisons []struct {
|
type comparisons []struct {
|
||||||
desired, actual interface{}
|
desired, actual any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *comparisons) add(desired, actual interface{}) {
|
func (c *comparisons) add(desired, actual any) {
|
||||||
if desired != nil {
|
if desired != nil {
|
||||||
*c = append(*c,
|
*c = append(*c,
|
||||||
struct{ desired, actual interface{} }{
|
struct{ desired, actual any }{
|
||||||
desired, actual,
|
desired, actual,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@@ -2,6 +2,7 @@ package prop
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,10 +56,8 @@ type StringOneOf []string
|
|||||||
|
|
||||||
// Compare implements StringConstraint.
|
// Compare implements StringConstraint.
|
||||||
func (f StringOneOf) Compare(a string) (float64, bool) {
|
func (f StringOneOf) Compare(a string) (float64, bool) {
|
||||||
for _, ff := range f {
|
if slices.Contains(f, a) {
|
||||||
if ff == a {
|
return 0.0, true
|
||||||
return 0.0, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1.0, false
|
return 1.0, false
|
||||||
}
|
}
|
||||||
|
6
track.go
6
track.go
@@ -203,8 +203,10 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
|
|||||||
encodedReader.Close()
|
encodedReader.Close()
|
||||||
|
|
||||||
// When there's another call to unbind, it won't block since we remove the current ctx from active connections
|
// When there's another call to unbind, it won't block since we remove the current ctx from active connections
|
||||||
track.removeActivePeerConnection(ctx.ID())
|
signalCh := track.removeActivePeerConnection(ctx.ID())
|
||||||
close(signalCh)
|
if signalCh != nil {
|
||||||
|
close(signalCh)
|
||||||
|
}
|
||||||
if doneCh != nil {
|
if doneCh != nil {
|
||||||
close(doneCh)
|
close(doneCh)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user