From d481aea85a07b1dabe2fe9b138869c6f3d0ed02b Mon Sep 17 00:00:00 2001 From: Damontecres Date: Thu, 9 Jul 2026 13:15:42 -0400 Subject: [PATCH] Experimental option to transcode surround sound audio to AC3 (#947) ## Description Adds an experimental setting to prefer transcoding audio with more than 2 channels to AC3. Stereo audio does not transcode. This is basically a workaround for HDMI ARC (not eARC) or optical audio devices because instead of 2 uncompressed PCM channels, 6 AC3 channels (ie 5.1) is possible. An downside of enabling this is that embedded subtitles be burned in causing a video transcode as well. ### Related issues Fixes #255 Incorporates some code from #1410 ### Testing Just on the emulator so far to observe if the settings trigger the transcode or not ## Screenshots N/A ## AI or LLM usage None --------- Co-authored-by: darkflame91 --- .../wholphin/preferences/AppPreference.kt | 23 ++++- .../preferences/ExperimentalPreference.kt | 26 ++++++ .../wholphin/services/DeviceProfileService.kt | 34 ++++---- .../wholphin/services/MediaReportService.kt | 13 ++- .../wholphin/services/MusicService.kt | 54 ++++++++++-- .../wholphin/ui/playback/PlaybackViewModel.kt | 18 +++- .../ui/preferences/PreferencesContent.kt | 2 +- .../subtitle/SubtitlePreferencesContent.kt | 2 +- .../util/profile/DeviceProfileUtils.kt | 84 +++++++++++++++---- app/src/main/proto/WholphinDataStore.proto | 1 + app/src/main/res/values/strings.xml | 2 + 11 files changed, 205 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index f573c4a7..5784d69b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -53,7 +53,10 @@ sealed interface AppPreference { value: T?, ): String? = null - fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid + fun validate( + prefs: Pref, + value: T, + ): PreferenceValidation = PreferenceValidation.Valid companion object { val SkipForward = @@ -391,18 +394,25 @@ sealed interface AppPreference { defaultValue = true, getter = { it.playbackPreferences.overrides.ac3Supported }, setter = { prefs, value -> - prefs.updatePlaybackOverrides { ac3Supported = value } + if (!value) prefs.updateExperimentalPreferences { preferAc3Surround = false } + prefs.updatePlaybackOverrides { + ac3Supported = value + } }, summaryOn = R.string.enabled, summaryOff = R.string.disabled, ) + val DownMixStereo = AppSwitchPreference( title = R.string.downmix_stereo, defaultValue = false, getter = { it.playbackPreferences.overrides.downmixStereo }, setter = { prefs, value -> - prefs.updatePlaybackOverrides { downmixStereo = value } + if (value) prefs.updateExperimentalPreferences { preferAc3Surround = false } + prefs.updatePlaybackOverrides { + downmixStereo = value + } }, summaryOn = R.string.enabled, summaryOff = R.string.disabled, @@ -1311,11 +1321,16 @@ data class AppSwitchPreference( override val defaultValue: Boolean, override val getter: (prefs: Pref) -> Boolean, override val setter: (prefs: Pref, value: Boolean) -> Pref, - val validator: (value: Boolean) -> PreferenceValidation = { PreferenceValidation.Valid }, + val validator: (prefs: Pref, value: Boolean) -> PreferenceValidation = { _, _ -> PreferenceValidation.Valid }, @param:StringRes val summary: Int? = null, @param:StringRes val summaryOn: Int? = null, @param:StringRes val summaryOff: Int? = null, ) : AppPreference { + override fun validate( + prefs: Pref, + value: Boolean, + ): PreferenceValidation = validator.invoke(prefs, value) + override fun summary( context: Context, value: Boolean?, diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt index 18e38f79..3950d626 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt @@ -4,6 +4,7 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption +import com.github.damontecres.wholphin.ui.preferences.PreferenceValidation object ExperimentalPreference { val Enable = @@ -35,6 +36,30 @@ object ExperimentalPreference { summaryOn = R.string.enabled, summaryOff = R.string.disabled, ) + + val PreferAc3ForSurround = + AppSwitchPreference( + title = R.string.prefer_ac3_for_surround, + defaultValue = true, + getter = { it.experimentalPreferences.preferAc3Surround }, + setter = { prefs, value -> + prefs.updateExperimentalPreferences { + preferAc3Surround = value + } + }, + summary = R.string.prefer_ac3_for_surround_summary, + validator = { prefs, value -> + prefs.playbackPreferences.overrides.let { + if (value && !it.ac3Supported) { + PreferenceValidation.Invalid("AC3 support is not enabled") + } else if (value && it.downmixStereo) { + PreferenceValidation.Invalid("Always downmixing to stereo") + } else { + PreferenceValidation.Valid + } + } + }, + ) } val experimentalPreferences = @@ -45,6 +70,7 @@ val experimentalPreferences = preferences = listOf( ExperimentalPreference.VideoTunneling, + ExperimentalPreference.PreferAc3ForSurround, ), ), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt index ce4ea7dc..a7dfe235 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt @@ -1,8 +1,10 @@ package com.github.damontecres.wholphin.services import android.content.Context +import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AssPlaybackMode -import com.github.damontecres.wholphin.preferences.PlaybackPreferences +import com.github.damontecres.wholphin.preferences.ExperimentalPreferences +import com.github.damontecres.wholphin.preferences.PlaybackOverrides import com.github.damontecres.wholphin.util.WholphinDispatchers import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest import com.github.damontecres.wholphin.util.profile.createDeviceProfile @@ -34,20 +36,17 @@ class DeviceProfileService private var deviceProfile: DeviceProfile? = null suspend fun getOrCreateDeviceProfile( - prefs: PlaybackPreferences, + appPrefs: AppPreferences, serverVersion: ServerVersion?, ): DeviceProfile = withContext(WholphinDispatchers.Default) { + val prefs = appPrefs.playbackPreferences mutex.withLock { val newConfig = DeviceProfileConfiguration( maxBitrate = prefs.maxBitrate.toInt(), - isAC3Enabled = prefs.overrides.ac3Supported, - downMixAudio = prefs.overrides.downmixStereo, - assPlaybackMode = prefs.overrides.assPlaybackMode, - pgsDirectPlay = prefs.overrides.directPlayPgs, - dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL, - decodeAv1 = prefs.overrides.decodeAv1, + overrides = prefs.overrides, + experimental = appPrefs.experimentalPreferences, jellyfinTenEleven = serverVersion != null && serverVersion >= ServerVersion(10, 11, 0), ) @@ -57,12 +56,13 @@ class DeviceProfileService createDeviceProfile( mediaTest = mediaCodecCapabilitiesTest, maxBitrate = newConfig.maxBitrate, - isAC3Enabled = newConfig.isAC3Enabled, - downMixAudio = newConfig.downMixAudio, - assDirectPlay = newConfig.assPlaybackMode != AssPlaybackMode.ASS_TRANSCODE, - pgsDirectPlay = newConfig.pgsDirectPlay, - dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay, + isAC3Enabled = newConfig.overrides.ac3Supported, + downMixAudio = newConfig.overrides.downmixStereo, + assDirectPlay = newConfig.overrides.assPlaybackMode != AssPlaybackMode.ASS_TRANSCODE, + pgsDirectPlay = newConfig.overrides.directPlayPgs, + dolbyVisionELDirectPlay = newConfig.overrides.directPlayDolbyVisionEL, decodeAv1 = prefs.overrides.decodeAv1, + preferAc3ForSurround = appPrefs.experimentalPreferences.preferAc3Surround, jellyfinTenEleven = newConfig.jellyfinTenEleven, ) } @@ -76,11 +76,7 @@ class DeviceProfileService */ data class DeviceProfileConfiguration( val maxBitrate: Int, - val isAC3Enabled: Boolean, - val downMixAudio: Boolean, - val assPlaybackMode: AssPlaybackMode, - val pgsDirectPlay: Boolean, - val dolbyVisionELDirectPlay: Boolean, - val decodeAv1: Boolean, + val overrides: PlaybackOverrides, + val experimental: ExperimentalPreferences, val jellyfinTenEleven: Boolean, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MediaReportService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MediaReportService.kt index eeae86d9..67ef91e8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MediaReportService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MediaReportService.kt @@ -61,10 +61,10 @@ class MediaReportService .getItem(itemId = item.id) .content.mediaSources val sourcesJson = json.encodeToString(sources) - val playbackPrefs = userPreferencesService.getCurrent().appPreferences.playbackPreferences + val appPreferences = userPreferencesService.getCurrent().appPreferences val serverVersion = serverRepository.currentServer?.serverVersion val deviceProfile = - deviceProfileService.getOrCreateDeviceProfile(playbackPrefs, serverVersion) + deviceProfileService.getOrCreateDeviceProfile(appPreferences, serverVersion) val deviceProfileJson = json.encodeToString(deviceProfile) val body = """ @@ -76,7 +76,14 @@ class MediaReportService model=${Build.MODEL} apiLevel=${Build.VERSION.SDK_INT} - playbackPrefs=${playbackPrefs.toString().replace("\n", ", ").replace("\t", " ")} + playbackPrefs=${ + appPreferences.playbackPreferences.toString().replace("\n", ", ") + .replace("\t", " ") + } + experimental=${ + appPreferences.experimentalPreferences.toString().replace("\n", ", ") + .replace("\t", " ") + } deviceProfile=$deviceProfileJson diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt index c1aeb280..eea66307 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt @@ -15,6 +15,7 @@ import com.github.damontecres.wholphin.data.model.AudioItem import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope import com.github.damontecres.wholphin.ui.DefaultItemFields +import com.github.damontecres.wholphin.ui.gt import com.github.damontecres.wholphin.ui.main.settings.MoveDirection import com.github.damontecres.wholphin.ui.onMain import com.github.damontecres.wholphin.ui.seekBack @@ -25,6 +26,7 @@ import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.PlaybackItemState import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.WholphinDispatchers +import com.github.damontecres.wholphin.util.profile.Codec import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope @@ -44,6 +46,7 @@ import org.jellyfin.sdk.api.client.extensions.universalAudioApi import org.jellyfin.sdk.api.sockets.subscribe import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.ImageType +import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.PlayMethod import org.jellyfin.sdk.model.api.PlaystateCommand import org.jellyfin.sdk.model.api.PlaystateMessage @@ -72,6 +75,7 @@ class MusicService private val playerFactory: PlayerFactory, private val serverRepository: ServerRepository, private val imageUrlService: ImageUrlService, + private val userPreferencesService: UserPreferencesService, ) { private val _state = MutableStateFlow(MusicServiceState.EMPTY) val state: StateFlow = _state @@ -92,6 +96,11 @@ class MusicService private var activityTracker: TrackActivityPlaybackListener? = null private var websocketJob: Job? = null + private suspend fun preferAc3Surround() = + userPreferencesService + .getCurrent() + .appPreferences.experimentalPreferences.preferAc3Surround + /** * Start music playback * @@ -195,10 +204,11 @@ class MusicService shuffled: Boolean, ) { Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled) + val preferAc3Surround = preferAc3Surround() val mediaItems = items .filter { it.type == BaseItemKind.AUDIO } - .map(::convert) + .map { convert(it, preferAc3Surround) } withContext(WholphinDispatchers.Main) { player.setMediaItems(mediaItems) player.shuffleModeEnabled = shuffled @@ -215,7 +225,8 @@ class MusicService index: Int? = null, ) { if (item.type == BaseItemKind.AUDIO) { - val mediaItem = convert(item) + val preferAc3Surround = preferAc3Surround() + val mediaItem = convert(item, preferAc3Surround) withContext(WholphinDispatchers.Main) { if (index != null) { player.addMediaItem(index, mediaItem) @@ -242,6 +253,7 @@ class MusicService list: BlockingList, startIndex: Int, ) = loading { + val preferAc3Surround = preferAc3Surround() var remaining = startIndex list.indices .chunked(25) @@ -252,7 +264,7 @@ class MusicService list .getBlocking(it) ?.takeIf { it.type == BaseItemKind.AUDIO } - ?.let(::convert) + ?.let { convert(it, preferAc3Surround) } } else { Timber.v("Skipping $remaining") remaining-- @@ -268,12 +280,36 @@ class MusicService /** * Converts a [BaseItem] into a [MediaItem] setting an [AudioItem] as its tag */ - private fun convert(audio: BaseItem): MediaItem { + private fun convert( + audio: BaseItem, + preferAc3Surround: Boolean, + ): MediaItem { + val needsAc3Transcode = + preferAc3Surround && + audio.data.mediaSources + ?.firstOrNull() + ?.mediaStreams + ?.any { stream -> + stream.type == MediaStreamType.AUDIO && + stream.channels.gt(2) + stream.codec != Codec.Audio.AC3 + } == true val url = - api.universalAudioApi.getUniversalAudioStreamUrl( - itemId = audio.id, - container = audioFormats, - ) + if (needsAc3Transcode) { + api.universalAudioApi.getUniversalAudioStreamUrl( + itemId = audio.id, + container = listOf("mka"), + transcodingContainer = "mka", + maxAudioChannels = 6, + transcodingAudioChannels = 6, + audioCodec = Codec.Audio.AC3, + ) + } else { + api.universalAudioApi.getUniversalAudioStreamUrl( + itemId = audio.id, + container = audioFormats, + ) + } Timber.i("url=%s", url) val imageUrl = audio.data.albumId?.let { albumId -> @@ -354,7 +390,7 @@ class MusicService * Play this item next after the current, ie add the item as the next index in the queue */ suspend fun playNext(song: BaseItem) { - val mediaItem = convert(song) + val mediaItem = convert(song, preferAc3Surround()) onMain { player.addMediaItem(state.value.currentIndex + 1, mediaItem) if (player.mediaItemCount == 1) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 37d3e5ad..94529bd6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -54,6 +54,7 @@ import com.github.damontecres.wholphin.services.ScreensaverService import com.github.damontecres.wholphin.services.StreamChoiceService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.ui.formatBitrate +import com.github.damontecres.wholphin.ui.gt import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO @@ -646,7 +647,7 @@ class PlaybackViewModel deviceProfile = if (currentPlayer.value!!.backend == PlayerBackend.EXO_PLAYER) { deviceProfileService.getOrCreateDeviceProfile( - preferences.appPreferences.playbackPreferences, + preferences.appPreferences, serverRepository.currentServer?.serverVersion, ) } else { @@ -855,9 +856,24 @@ class PlaybackViewModel // TODO there's probably no reason why we can't add external subtitles? Timber.v("changeStreams direct play") + // TODO Better way to handle unsupported types in general is needed + // This is a workaround for switching to a non AC3 track when the user wants audio transcoded to AC3 + if (preferences.appPreferences.experimentalPreferences.preferAc3Surround && audioIndex != null) { + currentPlayback.mediaSourceInfo.mediaStreams + .orEmpty() + .firstOrNull { it.index == audioIndex } + ?.let { + if (it.channels.gt(2) && it.codec != Codec.Audio.AC3) { + // User wants to transcode audio into AC3 + return@withContext false + } + } + } + val source = currentPlayback.mediaSourceInfo val externalSubtitle = source.findExternalSubtitle(subtitleIndex) + // TODO there's probably no reason why we can't add external subtitles? if (externalSubtitle == null) { val result = withContext(WholphinDispatchers.Main) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index b08e9508..050e7798 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -621,7 +621,7 @@ fun PreferencesContent( value = value, onNavigate = viewModel.navigationManager::navigateTo, onValueChange = { newValue -> - val validation = pref.validate(newValue) + val validation = pref.validate(preferences, newValue) when (validation) { is PreferenceValidation.Invalid -> { // TODO? diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt index 02f70d11..f130016f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitlePreferencesContent.kt @@ -162,7 +162,7 @@ fun SubtitlePreferencesContent( value = value, onNavigate = viewModel.navigationManager::navigateTo, onValueChange = { newValue -> - val validation = pref.validate(newValue) + val validation = pref.validate(preferences, newValue) when (validation) { is PreferenceValidation.Invalid -> { // TODO? diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt index a18d1805..d2efeb30 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt @@ -71,6 +71,7 @@ fun createDeviceProfile( dolbyVisionELDirectPlay: Boolean, decodeAv1: Boolean, jellyfinTenEleven: Boolean, + preferAc3ForSurround: Boolean, ) = buildDeviceProfile { val allowedAudioCodecs = when { @@ -132,31 +133,61 @@ fun createDeviceProfile( // / Transcoding profiles // Video - transcodingProfile { - type = DlnaProfileType.VIDEO - context = EncodingContext.STREAMING + if (preferAc3ForSurround) { + transcodingProfile { + type = DlnaProfileType.VIDEO + context = EncodingContext.STREAMING - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS - if (supportsHevc) videoCodec(Codec.Video.HEVC) - videoCodec(Codec.Video.H264) + if (supportsHevc) videoCodec(Codec.Video.HEVC) + videoCodec(Codec.Video.H264) - audioCodec(*allowedAudioCodecs) + audioCodec(Codec.Audio.AC3) - copyTimestamps = false - enableSubtitlesInManifest = true + copyTimestamps = false + enableSubtitlesInManifest = true + } + } else { + transcodingProfile { + type = DlnaProfileType.VIDEO + context = EncodingContext.STREAMING + + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS + + if (supportsHevc) videoCodec(Codec.Video.HEVC) + videoCodec(Codec.Video.H264) + + audioCodec(*allowedAudioCodecs) + + copyTimestamps = false + enableSubtitlesInManifest = true + } } // Audio - transcodingProfile { - type = DlnaProfileType.AUDIO - context = EncodingContext.STREAMING + if (preferAc3ForSurround) { + transcodingProfile { + type = DlnaProfileType.AUDIO + context = EncodingContext.STREAMING - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS - audioCodec(Codec.Audio.AAC) + audioCodec(Codec.Audio.AC3) + } + } else { + transcodingProfile { + type = DlnaProfileType.AUDIO + context = EncodingContext.STREAMING + + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS + + audioCodec(Codec.Audio.AAC) + } } // / Direct play profiles @@ -530,6 +561,27 @@ fun createDeviceProfile( } } + if (preferAc3ForSurround) { + supportedAudioCodecs + .filterNot { it == Codec.Audio.AC3 } + .forEach { audioCodec -> + codecProfile { + type = CodecType.VIDEO_AUDIO + codec = audioCodec + conditions { + ProfileConditionValue.AUDIO_CHANNELS lowerThanOrEquals 2 + } + } + codecProfile { + type = CodecType.AUDIO + codec = audioCodec + conditions { + ProfileConditionValue.AUDIO_CHANNELS lowerThanOrEquals 2 + } + } + } + } + // Audio channel profile codecProfile { type = CodecType.VIDEO_AUDIO diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 2eb47cef..4b1f6c34 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -215,6 +215,7 @@ message MusicPreferences { message ExperimentalPreferences { bool enabled = 1; bool video_tunneling_enabled = 2; + bool prefer_ac3_surround = 3; } message AppPreferences { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 13686543..54217145 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -551,6 +551,8 @@ Display presets Built-in presets to quickly style all rows Choose rows and images on the home page + + Use AC3 for surround sound audio Start after Animate Max age rating