mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01:00
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 <darkflame91@gmail.com>
This commit is contained in:
parent
f2287a85e8
commit
d481aea85a
11 changed files with 205 additions and 54 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MusicServiceState> = _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<BaseItem?>,
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue