8000 Enable Android simulcast (#731) · RowgerGo/flutter-webrtc@dcf3b7c · GitHub
[go: up one dir, main page]

Skip to content

Commit dcf3b7c

Browse files
authored
Enable Android simulcast (flutter-webrtc#731)
* enable simulcast * turn on kotlin compiler
1 parent c47593c commit dcf3b7c

File tree

4 files changed

+243
-3
lines changed

4 files changed

+243
-3
lines changed

NOTICE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
###################################################################################
2+
3+
The following modifications follow Apache License 2.0 from shiguredo.
4+
5+
SimulcastVideoEncoderFactoryWrapper.kt
6+
7+
Apache License 2.0
8+
9+
Copyright 2017, Lyo Kato <lyo.kato at gmail.com> (Original Author)
10+
Copyright 2017-2021, Shiguredo Inc.
11+
12+
Licensed under the Apache License, Version 2.0 (the "License");
13+
you may not use this file except in compliance with the License.
14+
You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS,
20+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
See the License for the specific language governing permissions and
22+
limitations under the License.
23+
24+
#####################################################################################

android/build.gradle

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ group 'com.cloudwebrtc.webrtc'
22
version '1.0-SNAPSHOT'
33

44
buildscript {
5+
ext.kotlin_version = '1.3.50'
56
repositories {
67
google()
78
jcenter()
89
}
910

1011
dependencies {
1112
classpath 'com.android.tools.build:gradle:3.6.3'
13+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1214
}
1315
}
1416

@@ -21,13 +23,14 @@ rootProject.allprojects {
2123
}
2224

2325
apply plugin: 'com.android.library'
26+
apply plugin: 'kotlin-android'
2427

2528
android {
2629
compileSdkVersion 30
2730

2831
defaultConfig {
2932
minSdkVersion 21
30-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
33+
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
3134
consumerProguardFiles 'proguard-rules.pro'
3235
}
3336

@@ -39,9 +42,14 @@ android {
3942
sourceCompatibility JavaVersion.VERSION_1_8
4043
targetCompatibility JavaVersion.VERSION_1_8
4144
}
45+
46+
kotlinOptions {
47+
jvmTarget = '1.8'
48+
}
4249
}
4350

4451
dependencies {
4552
implementation 'com.github.webrtc-sdk:android:92.4515.01'
46-
implementation "androidx.annotation:annotation:1.1.0"
53+
implementation 'androidx.annotation:annotation:1.1.0'
54+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
4755
}

android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java

Lines changed: 2 a 57A6 dditions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
1919
import com.cloudwebrtc.webrtc.utils.EglUtils;
2020
import com.cloudwebrtc.webrtc.utils.ObjectType;
21+
import com.cloudwebrtc.webrtc.SimulcastVideoEncoderFactoryWrapper;
2122

2223
import org.webrtc.AudioTrack;
2324
import org.webrtc.CryptoOptions;
@@ -155,7 +156,7 @@ private void ensureInitialized() {
155156

156157
mFactory = PeerConnectionFactory.builder()
157158
.setOptions(new Options())
158-
.setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglContext, false, true))
159+
.setVideoEncoderFactory(new SimulcastVideoEncoderFactoryWrapper(eglContext, true, false))
159160
.setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglContext))
160161
.setAudioDeviceModule(audioDeviceModule)
161162
.createPeerConnectionFactory();
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package com.cloudwebrtc.webrtc
2+
3+
import android.util.Log
4+
import org.webrtc.*
5+
import java.util.concurrent.*
6+
7+
/*
8+
Copyright 2017, Lyo Kato <lyo.kato at gmail.com> (Original Author)
9+
Copyright 2017-2021, Shiguredo Inc.
10+
11+
Licensed under the Apache License, Version 2.0 (the "License");
12+
you may not use this file except in compliance with the License.
13+
You may obtain a copy of the License at
14+
15+
http://www.apache.org/licenses/LICENSE-2.0
16+
17+
Unless required by applicable law or agreed to in writing, software
18+
distributed under the License is distributed on an "AS IS" BASIS,
19+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
See the License for the specific language governing permissions and
21+
limitations under the License.
22+
*/
23+
internal class SimulcastVideoEncoderFactoryWrapper(
24+
sharedContext: EglBase.Context?,
25+
enableIntelVp8Encoder: Boolean,
26+
enableH264HighProfile: Boolean
27+
) : VideoEncoderFactory {
28+
29+
/**
30+
* Factory that prioritizes software encoder.
31+
*
32+
* When the selected codec can't be handled by the software encoder,
33+
* it uses the hardware encoder as a fallback. However, this class is
34+
* primarily used to address an issue in libwebrtc, and does not have
35+
* purposeful usecase itself.
36+
*
37+
* To use simulcast in libwebrtc, SimulcastEncoderAdapter is used.
38+
* SimulcastEncoderAdapter takes in a primary and fallback encoder.
39+
* If HardwareVideoEncoderFactory and SoftwareVideoEncoderFactory are
40+
* passed in directly as primary and fallback, when H.264 is used,
41+
* libwebrtc will crash.
42+
*
43+
* This is because SoftwareVideoEncoderFactory does not handle H.264,
44+
* so [SoftwareVideoEncoderFactory.createEncoder] returns null, and
45+
* the libwebrtc side does not handle nulls, regardless of whether the
46+
* fallback is actually used or not.
47+
*
48+
* To avoid nulls, we simply pass responsibility over to the HardwareVideoEncoderFactory.
49+
* This results in HardwareVideoEncoderFactory being both the primary and fallback,
50+
* but there aren't any specific problems in doing so.
51+
*/
52+
private class Fallback(private val hardwareVideoEncoderFactory: VideoEncoderFactory) :
53+
VideoEncoderFactory {
54+
55+
private val softwareVideoEncoderFactory: VideoEncoderFactory = SoftwareVideoEncoderFactory()
56+
57+
override fun createEncoder(info: VideoCodecInfo): VideoEncoder? {
58+
val softwareEncoder = softwareVideoEncoderFactory.createEncoder(info)
59+
val hardwareEncoder = hardwareVideoEncoderFactory.createEncoder(info)
60+
return if (hardwareEncoder != null && softwareEncoder != null) {
61+
VideoEncoderFallback(hardwareEncoder, softwareEncoder)
62+
} else {
63+
softwareEncoder ?: hardwareEncoder
64+
}
65+
}
66+
67+
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
68+
val supportedCodecInfos: MutableList<VideoCodecInfo> = mutableListOf()
69+
supportedCodecInfos.addAll(softwareVideoEncoderFactory.supportedCodecs)
70+
supportedCodecInfos.addAll(hardwareVideoEncoderFactory.supportedCodecs)
71+
return supportedCodecInfos.toTypedArray()
72+
}
73+
74+
}
75+
76+
/**
77+
* Wraps each stream encoder and performs the following:
78+
* - Starts up a single thread
79+
* - When the width/height from [initEncode] doesn't match the frame buffer's,
80+
* scales the frame prior to encoding.
81+
* - Always calls the encoder on the thread.
82+
*/
83+
private class StreamEncoderWrapper(private val encoder: VideoEncoder) : VideoEncoder {
84+
85+
val executor: ExecutorService = Executors.newSingleThreadExecutor()
86+
var streamSettings: VideoEncoder.Settings? = null
87+
88+
override fun initEncode(
89+
settings: VideoEncoder.Settings,
90+
callback: VideoEncoder.Callback?
91+
): VideoCodecStatus {
92+
streamSettings = settings
93+
val future = executor.submit(Callable {
94+
// LKLog.i {
95+
// """initEncode() thread=${Thread.currentThread().name} [${Thread.currentThread().id}]
96+
// | streamSettings:
97+
// | numberOfCores=${settings.numberOfCores}
98+
// | width=${settings.width}
99+
// | height=${settings.height}
100+
// | startBitrate=${settings.startBitrate}
101+
// | maxFramerate=${settings.maxFramerate}
102+
// | automaticResizeOn=${settings.automaticResizeOn}
103+
// | numberOfSimulcastStreams=${settings.numberOfSimulcastStreams}
104+
// | lossNotification=${settings.capabilities.lossNotification}
105+
// """.trimMargin()
106+
// }
107+
return@Callable encoder.initEncode(settings, callback)
108+
})
109+
return future.get()
< E377 code>110+
}
111+
112+
override fun release(): VideoCodecStatus {
113+
val future = executor.submit(Callable { return@Callable encoder.release() })
114+
return future.get()
115+
}
116+
117+
override fun encode(
118+
frame: VideoFrame,
119+
encodeInfo: VideoEncoder.EncodeInfo?
120+
): VideoCodecStatus {
121+
val future = executor.submit(Callable {
122+
//LKLog.d { "encode() buffer=${frame.buffer}, thread=${Thread.currentThread().name} " +
123+
// "[${Thread.currentThread().id}]" }
124+
if (streamSettings == null) {
125+
return@Callable encoder.encode(frame, encodeInfo)
126+
} else if (frame.buffer.width == streamSettings!!.width) {
127+
return@Callable encoder.encode(frame, encodeInfo)
128+
} else {
129+
// The incoming buffer is different than the streamSettings received in initEncode()
130+
// Need to scale.
131+
val originalBuffer = frame.buffer
132+
// TODO: Do we need to handle when the scale factor is weird?
133+
val adaptedBuffer = originalBuffer.cropAndScale(
134+
0, 0, originalBuffer.width, originalBuffer.height,
135+
streamSettings!!.width, streamSettings!!.height
136+
)
137+
val adaptedFrame = VideoFrame(adaptedBuffer, frame.rotation, frame.timestampNs)
138+
val result = encoder.encode(adaptedFrame, encodeInfo)
139+
adaptedBuffer.release()
140+
return@Callable result
141+
}
142+
})
143+
return future.get()
144+
}
145+
146+
override fun setRateAllocation(
147+
allocation: VideoEncoder.BitrateAllocation?,
148+
frameRate: Int
149+
): VideoCodecStatus {
150+
val future = executor.submit(Callable {
151+
return@Callable encoder.setRateAllocation(
152+
allocation,
153+
frameRate
154+
)
155+
})
156+
return future.get()
157+
}
158+
159+
override fun getScalingSettings(): VideoEncoder.ScalingSettings {
160+
val future = executor.submit(Callable { return@Callable encoder.scalingSettings })
161+
return future.get()
162+
}
163+
164+
override fun getImplementationName(): String {
165+
val future = executor.submit(Callable { return@Callable encoder.implementationName })
166+
return future.get()
167+
}
168+
}
169+
170+
private class StreamEncoderWrapperFactory(private val factory: VideoEncoderFactory) :
171+
VideoEncoderFactory {
172+
override fun createEncoder(videoCodecInfo: VideoCodecInfo?): VideoEncoder? {
173+
val encoder = factory.createEncoder(videoCodecInfo)
174+
if (encoder == null) {
175+
return null
176+
}
177+
return StreamEncoderWrapper(encoder)
178+
}
179+
180+
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
181+
return factory.supportedCodecs
182+
}
183+
}
184+
185+
186+
private val primary: VideoEncoderFactory
187+
private val fallback: VideoEncoderFactory
188+
private val native: SimulcastVideoEncoderFactory
189+
190+
init {
191+
val hardwareVideoEncoderFactory = HardwareVideoEncoderFactory(
192+
sharedContext, enableIntelVp8Encoder, enableH264HighProfile
193+
)
194+
primary = StreamEncoderWrapperFactory(hardwareVideoEncoderFactory)
195+
fallback = StreamEncoderWrapperFactory(Fallback(primary))
196+
native = SimulcastVideoEncoderFactory(primary, fallback)
197+
}
198+
199+
override fun createEncoder(info: VideoCodecInfo?): VideoEncoder? {
200+
return native.createEncoder(info)
201+
}
202+
203+
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
204+
return native.supportedCodecs
205+
}
206+
207+
}

0 commit comments

Comments
 (0)
0