diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 924b69a951..9996b0c4f9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -53,6 +53,8 @@ `MediaItem.Builder.setImageDurationMs` mandatory for image export. * Add export support for gaps in sequences of audio EditedMediaItems. * Track Selection: + * `DefaultTrackSelector`: Prefer object-based audio over channel-based + audio when other factors are equal. * Extractors: * Fix preroll sample handling for non-keyframe media start positions when processing edit lists in MP4 files diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java index b34939927d..21c3fcdf10 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java @@ -3406,6 +3406,20 @@ public class DefaultTrackSelector extends MappingTrackSelector } } + private static boolean isObjectBasedAudio(Format format) { + if (format.sampleMimeType == null) { + return false; + } + switch (format.sampleMimeType) { + case MimeTypes.AUDIO_E_AC3_JOC: + case MimeTypes.AUDIO_AC4: + case MimeTypes.AUDIO_IAMF: + return true; + default: + return false; + } + } + private static boolean isDolbyAudio(Format format) { if (format.sampleMimeType == null) { return false; @@ -3726,6 +3740,7 @@ public class DefaultTrackSelector extends MappingTrackSelector private final int preferredMimeTypeMatchIndex; private final boolean usesPrimaryDecoder; private final boolean usesHardwareAcceleration; + private final boolean isObjectBasedAudio; public AudioTrackInfo( int rendererIndex, @@ -3770,6 +3785,7 @@ public class DefaultTrackSelector extends MappingTrackSelector getRoleFlagMatchScore(format.roleFlags, parameters.preferredAudioRoleFlags); hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0; isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + isObjectBasedAudio = isObjectBasedAudio(format); channelCount = format.channelCount; sampleRate = format.sampleRate; bitrate = format.bitrate; @@ -3877,6 +3893,7 @@ public class DefaultTrackSelector extends MappingTrackSelector .compareFalseFirst(this.usesPrimaryDecoder, other.usesPrimaryDecoder) .compareFalseFirst(this.usesHardwareAcceleration, other.usesHardwareAcceleration) // 5. Compare technical quality. + .compareFalseFirst(this.isObjectBasedAudio, other.isObjectBasedAudio) .compare(this.channelCount, other.channelCount, qualityOrdering) .compare(this.sampleRate, other.sampleRate, qualityOrdering); if (Util.areEqual(this.language, other.language)) { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java index b9b5c7f28d..b4fcfdddd7 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java @@ -988,6 +988,39 @@ public final class DefaultTrackSelectorTest { assertFixedSelection(result.selections[0], trackGroups, firstLanguageFormat); } + /** + * Tests that track selector will prefer audio tracks with object based audio over tracks with + * higher channel count when other factors are the same, and tracks are within renderer's + * capabilities. + */ + @Test + public void + selectTracks_audioChannelCountConstraintsDisabled_preferObjectBasedAudioBeforeChannelCount() + throws Exception { + Format channelBasedAudioWithHigherChannelCountFormat = + new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AC3).setChannelCount(6).build(); + Format objectBasedAudioWithLowerChannelCountFormat = + new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AC4).setChannelCount(2).build(); + TrackGroupArray trackGroups = + wrapFormats( + channelBasedAudioWithHigherChannelCountFormat, + objectBasedAudioWithLowerChannelCountFormat); + trackSelector.setParameters( + trackSelector + .buildUponParameters() + .setConstrainAudioChannelCountToDeviceCapabilities(false) + .build()); + + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + assertFixedSelection( + result.selections[0], trackGroups, objectBasedAudioWithLowerChannelCountFormat); + } + /** * Tests that track selector will prefer audio tracks with higher channel count over tracks with * higher sample rate when audio channel count constraints are disabled, other factors are the @@ -2779,37 +2812,37 @@ public final class DefaultTrackSelectorTest { public void selectTracks_withPreferredAudioMimeTypes_selectsTrackWithPreferredMimeType() throws Exception { Format formatAac = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build(); - Format formatAc4 = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AC4).build(); - Format formatEAc3 = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_E_AC3).build(); - TrackGroupArray trackGroups = wrapFormats(formatAac, formatAc4, formatEAc3); + Format formatOpus = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_OPUS).build(); + Format formatFlac = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_FLAC).build(); + TrackGroupArray trackGroups = wrapFormats(formatAac, formatOpus, formatFlac); trackSelector.setParameters( - trackSelector.buildUponParameters().setPreferredAudioMimeType(MimeTypes.AUDIO_AC4)); + trackSelector.buildUponParameters().setPreferredAudioMimeType(MimeTypes.AUDIO_OPUS)); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertFixedSelection(result.selections[0], trackGroups, formatAc4); + assertFixedSelection(result.selections[0], trackGroups, formatOpus); trackSelector.setParameters( trackSelector .buildUponParameters() - .setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC4, MimeTypes.AUDIO_AAC)); + .setPreferredAudioMimeTypes(MimeTypes.AUDIO_OPUS, MimeTypes.AUDIO_AAC)); result = trackSelector.selectTracks( new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertFixedSelection(result.selections[0], trackGroups, formatAc4); + assertFixedSelection(result.selections[0], trackGroups, formatOpus); trackSelector.setParameters( trackSelector .buildUponParameters() - .setPreferredAudioMimeTypes(MimeTypes.AUDIO_AMR, MimeTypes.AUDIO_E_AC3)); + .setPreferredAudioMimeTypes(MimeTypes.AUDIO_AMR, MimeTypes.AUDIO_FLAC)); result = trackSelector.selectTracks( new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertFixedSelection(result.selections[0], trackGroups, formatEAc3); + assertFixedSelection(result.selections[0], trackGroups, formatFlac); // Select first in the list if no preference is specified. trackSelector.setParameters(