Skip to content

Commit

Permalink
Add BlacklistAwareVideoDecoderFactory (#1204)
Browse files Browse the repository at this point in the history
* fix StreamCallActivity.isVideoCall behaviour

* add posibility to mock api_key and user_token

* add BlacklistAwareVideoDecoderFactory

* Rename isVideoSettingsEnabled method

---------

Co-authored-by: Liviu Timar <[email protected]>
  • Loading branch information
kanat and liviu-timar authored Oct 16, 2024
1 parent 59510b2 commit ebd1c70
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@
package io.getstream.video.android.data.services.stream

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import io.getstream.video.android.model.User
import io.getstream.video.android.models.UserCredentials
import io.getstream.video.android.models.builtInCredentials
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.create
import retrofit2.http.GET
import retrofit2.http.Query

interface StreamService {
fun interface StreamService {
@GET("api/auth/create-token")
suspend fun getAuthData(
@Query("environment") environment: String,
Expand All @@ -41,6 +44,15 @@ interface StreamService {
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()

val instance = retrofit.create<StreamService>()
private val serviceInstance = retrofit.create<StreamService>()

val instance = StreamService { environment, userId ->
User.builtInCredentials[userId]?.toAuthDataResponse()
?: serviceInstance.getAuthData(environment, userId)
}
}
}

private fun UserCredentials.toAuthDataResponse(): GetAuthDataResponse {
return GetAuthDataResponse(userId, apiKey, token)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ package io.getstream.video.android.models

import io.getstream.video.android.model.User

data class UserCredentials(val userId: String, val apiKey: String, val token: String)

public val User.Companion.builtInCredentials: Map<String, UserCredentials>
get() = mapOf()

public fun User.Companion.builtInUsers(): List<User> {
return listOf(
User(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public final class io/getstream/video/android/core/Call {
public final fun isLocalPin (Ljava/lang/String;)Z
public final fun isPinnedParticipant (Ljava/lang/String;)Z
public final fun isServerPin (Ljava/lang/String;)Z
public final fun isVideoEnabled ()Z
public final fun join (ZLio/getstream/video/android/core/CreateCallOptions;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun join$default (Lio/getstream/video/android/core/Call;ZLio/getstream/video/android/core/CreateCallOptions;ZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun leave ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,10 @@ public class Call(
return state.ownCapabilities.value.containsAll(elements)
}

fun isVideoEnabled(): Boolean {
return state.settings.value?.video?.enabled ?: false
}

fun isAudioProcessingEnabled(): Boolean {
return clientImpl.isAudioProcessingEnabled()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import io.getstream.video.android.core.model.StreamPeerType
import kotlinx.coroutines.CoroutineScope
import org.webrtc.AudioSource
import org.webrtc.AudioTrack
import org.webrtc.DefaultVideoDecoderFactory
import org.webrtc.BlacklistAwareVideoDecoderFactory
import org.webrtc.EglBase
import org.webrtc.Logging
import org.webrtc.ManagedAudioProcessingFactory
Expand Down Expand Up @@ -101,9 +101,7 @@ public class StreamPeerConnectionFactory(
* Default video decoder factory used to unpack video from the remote tracks.
*/
private val videoDecoderFactory by lazy {
DefaultVideoDecoderFactory(
eglBase.eglBaseContext,
)
BlacklistAwareVideoDecoderFactory(eglBase.eglBaseContext)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.webrtc

import android.media.MediaCodecInfo
import android.media.MediaCodecList
import io.getstream.log.taggedLogger

internal class BlacklistAwareVideoDecoderFactory(
eglContext: EglBase.Context?,
) : VideoDecoderFactory {

private val logger by taggedLogger("BlacklistAwareVideoDecoderFactory")

private val mediaCodecList by lazy { MediaCodecList(MediaCodecList.ALL_CODECS) }

/**
* Blacklist of codecs that are known to be buggy; we want to force software decoding for them.
*/
private val isHardwareDecoderBlacklisted: (MediaCodecInfo?) -> Boolean = {
it?.isExynosVP9() ?: false
}

private val allowedCodecPredicate: Predicate<MediaCodecInfo> = Predicate {
MediaCodecUtils.isHardwareAccelerated(it) || MediaCodecUtils.isSoftwareOnly(it)
}

private val hardwareVideoDecoderFactory: VideoDecoderFactory =
HardwareVideoDecoderFactory(eglContext)
private val softwareVideoDecoderFactory: VideoDecoderFactory = SoftwareVideoDecoderFactory()
private val platformSoftwareVideoDecoderFactory: VideoDecoderFactory =
PlatformSoftwareVideoDecoderFactory(eglContext)

override fun createDecoder(codecType: VideoCodecInfo): VideoDecoder? {
val type = VideoCodecMimeType.valueOf(codecType.getName())
val codec = findCodecForType(type)
logger.d { "[createDecoder] codecType: $codecType, codec: ${codec?.stringify()}" }

var softwareDecoder = softwareVideoDecoderFactory.createDecoder(codecType)
val hardwareDecoder = hardwareVideoDecoderFactory.createDecoder(codecType)
if (softwareDecoder == null) {
softwareDecoder = platformSoftwareVideoDecoderFactory.createDecoder(codecType)
}

if (isHardwareDecoderBlacklisted(codec)) {
logger.i { "[createDecoder] hardware decoder is blacklisted: ${codec?.stringify()}" }
return softwareDecoder
}

return if (hardwareDecoder != null && softwareDecoder != null) {
VideoDecoderFallback(softwareDecoder, hardwareDecoder)
} else {
hardwareDecoder ?: softwareDecoder
}
}

override fun getSupportedCodecs(): Array<VideoCodecInfo> {
val supportedCodecInfos = mutableSetOf<VideoCodecInfo>().apply {
addAll(softwareVideoDecoderFactory.supportedCodecs)
addAll(hardwareVideoDecoderFactory.supportedCodecs)
addAll(platformSoftwareVideoDecoderFactory.supportedCodecs)
}
logger.v { "[getSupportedCodecs] supportedCodecInfos: $supportedCodecInfos" }
return supportedCodecInfos.toTypedArray<VideoCodecInfo>()
}

private fun findCodecForType(type: VideoCodecMimeType): MediaCodecInfo? {
val codecInfos: List<MediaCodecInfo> = try {
mediaCodecList.codecInfos.filterNotNull().toList()
} catch (e: Throwable) {
logger.e(e) { "[findCodecForType] failed: $e" }
emptyList()
}
return codecInfos.firstOrNull {
!it.isEncoder && isSupportedCodec(it, type)
}
}

private fun isSupportedCodec(codec: MediaCodecInfo, type: VideoCodecMimeType): Boolean {
if (!MediaCodecUtils.codecSupportsType(codec, type)) {
return false
}
val colorFormat = MediaCodecUtils.selectColorFormat(
MediaCodecUtils.DECODER_COLOR_FORMATS,
codec.getCapabilitiesForType(type.mimeType()),
)
if (colorFormat == null) {
return false
}
return isCodecAllowed(codec)
}

private fun isCodecAllowed(info: MediaCodecInfo): Boolean {
return allowedCodecPredicate.test(info)
}
}

private fun MediaCodecInfo.isExynosVP9(): Boolean {
return !isEncoder && name.contains("exynos", ignoreCase = true) &&
name.contains("vp9", ignoreCase = true)
}

private fun MediaCodecInfo.stringify(): String {
return "MediaCodecInfo(" +
"name=$name, " +
"canonicalName=$canonicalName, " +
"isAlias=$isAlias, " +
"isVendor=$isVendor, " +
"isEncoder=$isEncoder, " +
"isHardwareAccelerated=$isHardwareAccelerated, " +
"isSoftwareOnly=$isSoftwareOnly, " +
"supportedTypes=${supportedTypes.joinToString()}" +
")"
}
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,9 @@ public abstract class StreamCallActivity : ComponentActivity() {

// Decision making
@StreamCallActivityDelicateApi
public open fun isVideoCall(call: Call): Boolean =
call.hasCapability(OwnCapability.SendVideo)
public open fun isVideoCall(call: Call): Boolean {
return call.hasCapability(OwnCapability.SendVideo) || call.isVideoEnabled()
}

// Picture in picture (for Video calls)
/**
Expand Down

0 comments on commit ebd1c70

Please sign in to comment.