Skip to content

Commit 35db157

Browse files
authored
fix: hw decoder rendering delay after frame resize (#18)
## Fix Includes GetStream/webrtc-android#302 ## Extra * build system modernization, and manifest updates. * updating Gradle configurations for better Kotlin and namespace support * Aligning encoder with Android SDK
1 parent a26d75a commit 35db157

File tree

11 files changed

+314
-62
lines changed

11 files changed

+314
-62
lines changed

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@ examples/GumTestApp_macOS/package-lock.json
1717
*.zip
1818
lib/
1919
src/*.js
20-
20+
# Android/IntelliJ
21+
#
22+
build/
23+
.idea
24+
.gradle
25+
local.properties
26+
*.iml
27+
*.hprof
28+
.cxx/

android/build.gradle

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
11
import java.nio.file.Paths
22

3-
apply plugin: 'com.android.library'
3+
buildscript {
4+
ext.getExtOrDefault = {name, fallback ->
5+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
6+
}
7+
8+
repositories {
9+
google()
10+
mavenCentral()
11+
maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
12+
}
13+
14+
dependencies {
15+
classpath("com.android.tools.build:gradle:7.3.1")
16+
// noinspection DifferentKotlinGradleVersion
17+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion', '1.8.10')}"
18+
}
19+
}
20+
21+
apply plugin: "com.android.library"
22+
apply plugin: "kotlin-android"
423

524
def safeExtGet(prop, fallback) {
625
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
726
}
827

28+
def supportsNamespace() {
29+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
30+
def major = parsed[0].toInteger()
31+
def minor = parsed[1].toInteger()
32+
33+
// Namespace support was added in 7.3.0
34+
return (major == 7 && minor >= 3) || major >= 8
35+
}
36+
937
android {
10-
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
11-
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
12-
namespace "com.oney.WebRTCModule"
38+
if (supportsNamespace()) {
39+
namespace "com.oney.WebRTCModule"
40+
41+
sourceSets {
42+
main {
43+
manifest.srcFile "src/main/AndroidManifestNew.xml"
44+
}
45+
}
1346
}
1447

1548
compileSdkVersion safeExtGet('compileSdkVersion', 24)
@@ -31,7 +64,8 @@ android {
3164
}
3265

3366
dependencies {
34-
api 'io.getstream:stream-webrtc-android:1.3.8'
67+
api 'io.getstream:stream-webrtc-android:1.3.9'
3568
implementation 'com.facebook.react:react-native:+'
69+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${getExtOrDefault('kotlinVersion', '1.8.10')}"
3670
implementation "androidx.core:core:1.7.0"
3771
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:tools="http://schemas.android.com/tools"
3+
>
4+
<application>
5+
<service
6+
android:name=".MediaProjectionService"
7+
android:foregroundServiceType="mediaProjection">
8+
</service>
9+
</application>
10+
</manifest>
Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package com.oney.WebRTCModule;
2-
3-
import android.os.Build.VERSION;
4-
import android.util.Log;
5-
62
import org.webrtc.EglBase;
73

84
public class EglUtils {
95
/**
106
* The root {@link EglBase} instance shared by the entire application for
117
* the sake of reducing the utilization of system resources (such as EGL
12-
* contexts). It selects between {@link EglBase10} and {@link EglBase14}
13-
* by performing a runtime check.
8+
* contexts).
149
*/
1510
private static EglBase rootEglBase;
1611

@@ -20,47 +15,13 @@ public class EglUtils {
2015
*/
2116
public static synchronized EglBase getRootEglBase() {
2217
if (rootEglBase == null) {
23-
// XXX EglBase14 will report that isEGL14Supported() but its
24-
// getEglConfig() will fail with a RuntimeException with message
25-
// "Unable to find any matching EGL config". Fall back to EglBase10
26-
// in the described scenario.
27-
EglBase eglBase = null;
28-
int[] configAttributes = EglBase.CONFIG_PLAIN;
29-
RuntimeException cause = null;
30-
31-
try {
32-
// WebRTC internally does this check in isEGL14Supported, but it's no longer exposed
33-
// in the public API
34-
if (VERSION.SDK_INT >= 18) {
35-
eglBase = EglBase.createEgl14(configAttributes);
36-
}
37-
} catch (RuntimeException ex) {
38-
// Fall back to EglBase10.
39-
cause = ex;
40-
}
41-
42-
if (eglBase == null) {
43-
try {
44-
eglBase = EglBase.createEgl10(configAttributes);
45-
} catch (RuntimeException ex) {
46-
// Neither EglBase14, nor EglBase10 succeeded to initialize.
47-
cause = ex;
48-
}
49-
}
50-
51-
if (cause != null) {
52-
Log.e(EglUtils.class.getName(), "Failed to create EglBase", cause);
53-
} else {
54-
rootEglBase = eglBase;
55-
}
18+
rootEglBase = EglBase.create();
5619
}
57-
5820
return rootEglBase;
5921
}
6022

6123
public static EglBase.Context getRootEglBaseContext() {
6224
EglBase eglBase = getRootEglBase();
63-
6425
return eglBase == null ? null : eglBase.getEglBaseContext();
6526
}
6627
}

android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
import com.facebook.react.modules.core.DeviceEventManagerModule;
2424
import com.oney.WebRTCModule.audio.AudioProcessingFactoryProvider;
2525
import com.oney.WebRTCModule.audio.AudioProcessingController;
26-
import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoDecoderFactory;
27-
import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
26+
import com.oney.WebRTCModule.webrtcutils.SelectiveVideoDecoderFactory;
2827

2928
import org.webrtc.*;
3029
import org.webrtc.audio.AudioDeviceModule;
3130
import org.webrtc.audio.JavaAudioDeviceModule;
3231

3332
import java.util.ArrayList;
33+
import java.util.Arrays;
3434
import java.util.HashMap;
3535
import java.util.List;
3636
import java.util.Map;
@@ -83,14 +83,9 @@ public WebRTCModule(ReactApplicationContext reactContext) {
8383
if (encoderFactory == null || decoderFactory == null) {
8484
// Initialize EGL context required for HW acceleration.
8585
EglBase.Context eglContext = EglUtils.getRootEglBaseContext();
86+
encoderFactory = new SimulcastAlignedVideoEncoderFactory(eglContext, true, true, ResolutionAdjustment.MULTIPLE_OF_16);
87+
decoderFactory = new SelectiveVideoDecoderFactory(eglContext, false, Arrays.asList("VP9", "AV1"));
8688

87-
if (eglContext != null) {
88-
encoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
89-
decoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
90-
} else {
91-
encoderFactory = new SoftwareVideoEncoderFactory();
92-
decoderFactory = new SoftwareVideoDecoderFactory();
93-
}
9489
}
9590

9691
if (adm == null) {
@@ -134,9 +129,11 @@ private PeerConnection getPeerConnection(int id) {
134129
}
135130

136131
void sendEvent(String eventName, @Nullable ReadableMap params) {
137-
getReactApplicationContext()
138-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
139-
.emit(eventName, params);
132+
if (getReactApplicationContext().hasActiveReactInstance()) {
133+
getReactApplicationContext()
134+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
135+
.emit(eventName, params);
136+
}
140137
}
141138

142139
private PeerConnection.IceServer createIceServer(String url) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.oney.WebRTCModule.webrtcutils
18+
19+
import org.webrtc.EglBase
20+
import org.webrtc.SoftwareVideoDecoderFactory
21+
import org.webrtc.VideoCodecInfo
22+
import org.webrtc.VideoDecoder
23+
import org.webrtc.VideoDecoderFactory
24+
25+
internal class SelectiveVideoDecoderFactory(
26+
sharedContext: EglBase.Context?,
27+
private var forceSWCodec: Boolean = false,
28+
private var forceSWCodecs: List<String> = listOf("VP9", "AV1"),
29+
) : VideoDecoderFactory {
30+
private val softwareVideoDecoderFactory = SoftwareVideoDecoderFactory()
31+
private val wrappedVideoDecoderFactory = WrappedVideoDecoderFactory(sharedContext, forceSWCodec)
32+
33+
/**
34+
* Set to true to force software codecs.
35+
*/
36+
fun setForceSWCodec(forceSWCodec: Boolean) {
37+
this.forceSWCodec = forceSWCodec
38+
}
39+
40+
/**
41+
* Set a list of codecs for which to use software codecs.
42+
*/
43+
fun setForceSWCodecList(forceSWCodecs: List<String>) {
44+
this.forceSWCodecs = forceSWCodecs
45+
}
46+
47+
override fun createDecoder(videoCodecInfo: VideoCodecInfo): VideoDecoder? {
48+
if (forceSWCodecs.isNotEmpty()) {
49+
if (forceSWCodecs.contains(videoCodecInfo.name)) {
50+
return softwareVideoDecoderFactory.createDecoder(videoCodecInfo)
51+
}
52+
}
53+
if (forceSWCodec) {
54+
return wrappedVideoDecoderFactory.createDecoder(videoCodecInfo)
55+
}
56+
return wrappedVideoDecoderFactory.createDecoder(videoCodecInfo)
57+
}
58+
59+
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
60+
return wrappedVideoDecoderFactory.supportedCodecs
61+
}
62+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.oney.WebRTCModule.webrtcutils
18+
19+
import org.webrtc.EglBase
20+
import org.webrtc.ResolutionAdjustment
21+
import org.webrtc.SimulcastAlignedVideoEncoderFactory
22+
import org.webrtc.SoftwareVideoEncoderFactory
23+
import org.webrtc.VideoCodecInfo
24+
import org.webrtc.VideoEncoder
25+
import org.webrtc.VideoEncoderFactory
26+
27+
internal class SelectiveVideoEncoderFactory(
28+
sharedContext: EglBase.Context?,
29+
enableIntelVp8Encoder: Boolean,
30+
enableH264HighProfile: Boolean,
31+
private var forceSWCodec: Boolean = false,
32+
private var forceSWCodecs: List<String> = listOf("VP9", "AV1"),
33+
) : VideoEncoderFactory {
34+
private val softwareVideoEncoderFactory = SoftwareVideoEncoderFactory()
35+
private val simulcastVideoEncoderFactoryWrapper: SimulcastAlignedVideoEncoderFactory
36+
37+
init {
38+
simulcastVideoEncoderFactoryWrapper =
39+
SimulcastAlignedVideoEncoderFactory(sharedContext, enableIntelVp8Encoder, enableH264HighProfile, ResolutionAdjustment.NONE)
40+
}
41+
42+
/**
43+
* Set to true to force software codecs.
44+
*/
45+
fun setForceSWCodec(forceSWCodec: Boolean) {
46+
this.forceSWCodec = forceSWCodec
47+
}
48+
49+
/**
50+
* Set a list of codecs for which to use software codecs.
51+
*/
52+
fun setForceSWCodecList(forceSWCodecs: List<String>) {
53+
this.forceSWCodecs = forceSWCodecs
54+
}
55+
56+
override fun createEncoder(videoCodecInfo: VideoCodecInfo): VideoEncoder? {
57+
if (forceSWCodec) {
58+
return softwareVideoEncoderFactory.createEncoder(videoCodecInfo)
59+
}
60+
if (forceSWCodecs.isNotEmpty()) {
61+
if (forceSWCodecs.contains(videoCodecInfo.name)) {
62+
return softwareVideoEncoderFactory.createEncoder(videoCodecInfo)
63+
}
64+
}
65+
return simulcastVideoEncoderFactoryWrapper.createEncoder(videoCodecInfo)
66+
}
67+
68+
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
69+
return if (forceSWCodec && forceSWCodecs.isEmpty()) {
70+
softwareVideoEncoderFactory.supportedCodecs
71+
} else {
72+
simulcastVideoEncoderFactoryWrapper.supportedCodecs
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)