Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance AVC and High 10 Profile Support #3930

Merged
merged 2 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package org.jellyfin.androidtv.util.profile
import org.jellyfin.androidtv.constant.Codec
import org.jellyfin.androidtv.util.profile.ProfileHelper.audioDirectPlayProfile
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceAV1CodecProfile
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceAVCCodecProfile
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceAVCLevelCodecProfiles
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcCodecProfile
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcLevelCodecProfiles
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoLevelProfileCondition
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoProfileCondition
import org.jellyfin.androidtv.util.profile.ProfileHelper.max1080pProfileConditions
import org.jellyfin.androidtv.util.profile.ProfileHelper.maxAudioChannelsCodecProfile
import org.jellyfin.androidtv.util.profile.ProfileHelper.photoDirectPlayProfile
Expand Down Expand Up @@ -154,15 +154,8 @@ class ExoPlayerProfile(

codecProfiles = buildList {
// H264 profile
add(CodecProfile().apply {
type = CodecType.Video
codec = Codec.Video.H264
conditions = buildList {
add(h264VideoProfileCondition)
add(h264VideoLevelProfileCondition)
if (disable4KVideo) addAll(max1080pProfileConditions)
}.toTypedArray()
})
add(deviceAVCCodecProfile)
addAll(deviceAVCLevelCodecProfiles)
// H264 ref frames profile
add(CodecProfile().apply {
type = CodecType.Video
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ import timber.log.Timber
class MediaCodecCapabilitiesTest {
private val mediaCodecList by lazy { MediaCodecList(MediaCodecList.REGULAR_CODECS) }

// AVC levels as reported by ffprobe are multiplied by 10, e.g. level 4.1 is 41. Level 1b is set to 9
private val avcLevelStrings = listOf(
CodecProfileLevel.AVCLevel1b to "9",
CodecProfileLevel.AVCLevel1 to "10",
CodecProfileLevel.AVCLevel11 to "11",
CodecProfileLevel.AVCLevel12 to "12",
CodecProfileLevel.AVCLevel13 to "13",
CodecProfileLevel.AVCLevel2 to "20",
CodecProfileLevel.AVCLevel21 to "21",
CodecProfileLevel.AVCLevel22 to "22",
CodecProfileLevel.AVCLevel3 to "30",
CodecProfileLevel.AVCLevel31 to "31",
CodecProfileLevel.AVCLevel32 to "32",
CodecProfileLevel.AVCLevel4 to "40",
CodecProfileLevel.AVCLevel41 to "41",
CodecProfileLevel.AVCLevel42 to "42",
CodecProfileLevel.AVCLevel5 to "50",
CodecProfileLevel.AVCLevel51 to "51",
CodecProfileLevel.AVCLevel52 to "52",
)

// HEVC levels as reported by ffprobe are multiplied by 30, e.g. level 4.1 is 123
private val hevcLevelStrings = listOf(
CodecProfileLevel.HEVCMainTierLevel1 to "30",
Expand Down Expand Up @@ -36,6 +57,30 @@ class MediaCodecCapabilitiesTest {
CodecProfileLevel.AV1Level5
)

fun supportsAVC(): Boolean = hasCodecForMime(MediaFormat.MIMETYPE_VIDEO_AVC)

fun supportsAVCHigh10(): Boolean = hasDecoder(
MediaFormat.MIMETYPE_VIDEO_AVC,
CodecProfileLevel.AVCProfileHigh10,
CodecProfileLevel.AVCLevel4
)

fun getAVCMainLevel(): String = getAVCLevelString(
CodecProfileLevel.AVCProfileMain
)

fun getAVCHigh10Level(): String = getAVCLevelString(
CodecProfileLevel.AVCProfileHigh10
)

private fun getAVCLevelString(profile: Int): String {
val level = getDecoderLevel(MediaFormat.MIMETYPE_VIDEO_AVC, profile)

return avcLevelStrings.asReversed().find { item: Pair<Int, String> ->
level >= item.first
}?.second ?: "0"
}

fun supportsHevc(): Boolean = hasCodecForMime(MediaFormat.MIMETYPE_VIDEO_HEVC)

fun supportsHevcMain10(): Boolean = hasDecoder(
Expand All @@ -52,12 +97,6 @@ class MediaCodecCapabilitiesTest {
CodecProfileLevel.HEVCProfileMain10
)

fun supportsAVCHigh10(): Boolean = hasDecoder(
MediaFormat.MIMETYPE_VIDEO_AVC,
CodecProfileLevel.AVCProfileHigh10,
CodecProfileLevel.AVCLevel4
)

private fun getHevcLevelString(profile: Int): String {
val level = getDecoderLevel(MediaFormat.MIMETYPE_VIDEO_HEVC, profile)

Expand Down
136 changes: 101 additions & 35 deletions app/src/main/java/org/jellyfin/androidtv/util/profile/ProfileHelper.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jellyfin.androidtv.util.profile

import org.jellyfin.androidtv.constant.Codec
import org.jellyfin.androidtv.util.DeviceUtils
import org.jellyfin.apiclient.model.dlna.CodecProfile
import org.jellyfin.apiclient.model.dlna.CodecType
import org.jellyfin.apiclient.model.dlna.DirectPlayProfile
Expand All @@ -14,11 +13,6 @@ import org.jellyfin.apiclient.model.dlna.SubtitleProfile
import timber.log.Timber

object ProfileHelper {
// H264 codec levels https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
private const val H264_LEVEL_4_1 = "41"
private const val H264_LEVEL_5_1 = "51"
private const val H264_LEVEL_5_2 = "52"

private val MediaTest by lazy { MediaCodecCapabilitiesTest() }

val deviceAV1CodecProfile by lazy {
Expand Down Expand Up @@ -63,6 +57,107 @@ object ProfileHelper {
}
}

val supportsAVC by lazy {
MediaTest.supportsAVC()
}

val supportsAVCHigh10 by lazy {
MediaTest.supportsAVCHigh10()
}

val deviceAVCCodecProfile by lazy {
CodecProfile().apply {
type = CodecType.Video
codec = Codec.Video.H264

conditions = when {
!supportsAVC -> {
// If AVC is not supported, exclude all AVC profiles
Timber.i("*** Does NOT support AVC")
arrayOf(
ProfileCondition(
ProfileConditionType.Equals,
ProfileConditionValue.VideoProfile,
"none"
)
)
}
else -> {
// If AVC is supported, include all relevant profiles
Timber.i("*** Supports AVC")
arrayOf(
ProfileCondition(
ProfileConditionType.EqualsAny,
ProfileConditionValue.VideoProfile,
listOfNotNull(
"High",
"Main",
"Baseline",
"Constrained Baseline",
if (supportsAVCHigh10) "High 10" else null
).joinToString("|")
)
)
}
}
}
}

val deviceAVCLevelCodecProfiles by lazy {
buildList {
if (supportsAVC) {
add(CodecProfile().apply {
type = CodecType.Video
codec = Codec.Video.H264

applyConditions = arrayOf(
ProfileCondition(
ProfileConditionType.EqualsAny,
ProfileConditionValue.VideoProfile,
listOfNotNull(
"High",
"Main",
"Baseline",
"Constrained Baseline"
).joinToString("|")
)
)

conditions = arrayOf(
ProfileCondition(
ProfileConditionType.LessThanEqual,
ProfileConditionValue.VideoLevel,
MediaTest.getAVCMainLevel()
)
)
})

if (supportsAVCHigh10) {
add(CodecProfile().apply {
type = CodecType.Video
codec = Codec.Video.H264

applyConditions = arrayOf(
ProfileCondition(
ProfileConditionType.Equals,
ProfileConditionValue.VideoProfile,
"High 10"
)
)

conditions = arrayOf(
ProfileCondition(
ProfileConditionType.LessThanEqual,
ProfileConditionValue.VideoLevel,
MediaTest.getAVCHigh10Level()
)
)
})
}
}
}
}

val supportsHevc by lazy {
MediaTest.supportsHevc()
}
Expand Down Expand Up @@ -159,35 +254,6 @@ object ProfileHelper {
}
}

val h264VideoLevelProfileCondition by lazy {
ProfileCondition(
ProfileConditionType.LessThanEqual,
ProfileConditionValue.VideoLevel,
when {
// https://developer.amazon.com/docs/fire-tv/device-specifications.html
DeviceUtils.isFireTvStick4k -> H264_LEVEL_5_2
DeviceUtils.isFireTv4k -> H264_LEVEL_5_2
DeviceUtils.isFireTv -> H264_LEVEL_4_1
DeviceUtils.isShieldTv -> H264_LEVEL_5_2
else -> H264_LEVEL_5_1
}
)
}

val h264VideoProfileCondition by lazy {
ProfileCondition(
ProfileConditionType.EqualsAny,
ProfileConditionValue.VideoProfile,
listOfNotNull(
"high",
"main",
"baseline",
"constrained baseline",
if (MediaTest.supportsAVCHigh10()) "high 10" else null
).joinToString("|")
)
}

val max1080pProfileConditions by lazy {
arrayOf(
ProfileCondition(
Expand Down
Loading