diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index 6c28504521c..a911d78e627 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -106,6 +106,7 @@ public class DefaultRenderersFactory implements RenderersFactory { private MediaCodecSelector mediaCodecSelector; private boolean enableFloatOutput; private boolean enableAudioTrackPlaybackParams; + private boolean mapDV7ToHevc; /** * @param context A {@link Context}. @@ -116,6 +117,7 @@ public DefaultRenderersFactory(Context context) { extensionRendererMode = EXTENSION_RENDERER_MODE_OFF; allowedVideoJoiningTimeMs = DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS; mediaCodecSelector = MediaCodecSelector.DEFAULT; + mapDV7ToHevc = false; } /** @@ -135,6 +137,12 @@ public final DefaultRenderersFactory setExtensionRendererMode( return this; } + @CanIgnoreReturnValue + public DefaultRenderersFactory setMapDV7ToHevc(boolean mapDV7ToHevc) { + this.mapDV7ToHevc = mapDV7ToHevc; + return this; + } + /** * Enables {@link androidx.media3.exoplayer.mediacodec.MediaCodecRenderer} instances to operate * their {@link MediaCodec} in asynchronous mode and perform asynchronous queueing. @@ -347,7 +355,8 @@ protected void buildVideoRenderers( enableDecoderFallback, eventHandler, eventListener, - MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY, + mapDV7ToHevc); out.add(videoRenderer); if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaExtractorCompat.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaExtractorCompat.java index 52e4c63213e..2dd7d29bc18 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaExtractorCompat.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaExtractorCompat.java @@ -553,7 +553,7 @@ private void onSampleQueueFormatInitialized( /* compatibilityTrackMimeType= */ null)); @Nullable String compatibilityTrackMimeType = - MediaCodecUtil.getAlternativeCodecMimeType(newUpstreamFormat); + MediaCodecUtil.getAlternativeCodecMimeType(newUpstreamFormat, false); if (compatibilityTrackMimeType != null) { mediaExtractorSampleQueue.setCompatibilityTrackIndex(tracks.size()); tracks.add( diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java index 1f10039a09b..02cc68f76a8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java @@ -290,7 +290,7 @@ public boolean isFormatFunctionallySupported(Format format) { private boolean isSampleMimeTypeSupported(Format format) { return mimeType.equals(format.sampleMimeType) - || mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format)); + || mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format, /* mapDV7ToHevc */ false)); } private boolean isCodecProfileAndLevelSupported( diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java index 8de5be25b08..a34654266f4 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java @@ -222,7 +222,7 @@ public static List getDecoderInfosSoftMatch( format.sampleMimeType, requiresSecureDecoder, requiresTunnelingDecoder); List alternativeDecoderInfos = getAlternativeDecoderInfos( - mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder); + mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder, /* mapDV7ToHevc */ false); return ImmutableList.builder() .addAll(decoderInfos) .addAll(alternativeDecoderInfos) @@ -251,9 +251,10 @@ public static List getAlternativeDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder, - boolean requiresTunnelingDecoder) + boolean requiresTunnelingDecoder, + boolean mapDV7ToHevc) throws DecoderQueryException { - @Nullable String alternativeMimeType = getAlternativeCodecMimeType(format); + @Nullable String alternativeMimeType = getAlternativeCodecMimeType(format, mapDV7ToHevc); if (alternativeMimeType == null) { return ImmutableList.of(); } @@ -344,7 +345,7 @@ public static Pair getCodecProfileAndLevel(Format format) { * exists. */ @Nullable - public static String getAlternativeCodecMimeType(Format format) { + public static String getAlternativeCodecMimeType(Format format, boolean mapDV7ToHevc) { if (MimeTypes.AUDIO_E_AC3_JOC.equals(format.sampleMimeType)) { // E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D. return MimeTypes.AUDIO_E_AC3; @@ -358,7 +359,8 @@ public static String getAlternativeCodecMimeType(Format format) { if (codecProfileAndLevel != null) { int profile = codecProfileAndLevel.first; if (profile == CodecProfileLevel.DolbyVisionProfileDvheDtr - || profile == CodecProfileLevel.DolbyVisionProfileDvheSt) { + || profile == CodecProfileLevel.DolbyVisionProfileDvheSt + || (mapDV7ToHevc && profile == CodecProfileLevel.DolbyVisionProfileDvheDtb)) { return MimeTypes.VIDEO_H265; } else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) { return MimeTypes.VIDEO_H264; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java index d019636edd6..649f9bdfccb 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java @@ -149,6 +149,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer private final boolean deviceNeedsNoPostProcessWorkaround; private final VideoFrameReleaseControl videoFrameReleaseControl; private final VideoFrameReleaseControl.FrameReleaseInfo videoFrameReleaseInfo; + private final boolean mapDV7ToHevc; private @MonotonicNonNull CodecMaxValues codecMaxValues; private boolean codecNeedsSetOutputSurfaceWorkaround; @@ -199,7 +200,8 @@ public MediaCodecVideoRenderer( allowedJoiningTimeMs, /* eventHandler= */ null, /* eventListener= */ null, - /* maxDroppedFramesToNotify= */ 0); + /* maxDroppedFramesToNotify= */ 0, + /* mapDV7ToHevc= */ false); } /** @@ -219,7 +221,8 @@ public MediaCodecVideoRenderer( long allowedJoiningTimeMs, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, - int maxDroppedFramesToNotify) { + int maxDroppedFramesToNotify, + boolean mapDV7ToHevc) { this( context, MediaCodecAdapter.Factory.getDefault(context), @@ -229,7 +232,8 @@ public MediaCodecVideoRenderer( eventHandler, eventListener, maxDroppedFramesToNotify, - /* assumedMinimumCodecOperatingRate= */ 30); + /* assumedMinimumCodecOperatingRate= */ 30, + /* mapDV7ToHevc= */ mapDV7ToHevc); } /** @@ -263,7 +267,8 @@ public MediaCodecVideoRenderer( eventHandler, eventListener, maxDroppedFramesToNotify, - /* assumedMinimumCodecOperatingRate= */ 30); + /* assumedMinimumCodecOperatingRate= */ 30, + /* mapDV7ToHevc= */ false); } /** @@ -290,7 +295,8 @@ public MediaCodecVideoRenderer( boolean enableDecoderFallback, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, - int maxDroppedFramesToNotify) { + int maxDroppedFramesToNotify, + boolean mapDV7ToHevc) { this( context, codecAdapterFactory, @@ -300,7 +306,8 @@ public MediaCodecVideoRenderer( eventHandler, eventListener, maxDroppedFramesToNotify, - /* assumedMinimumCodecOperatingRate= */ 30); + /* assumedMinimumCodecOperatingRate= */ 30, + mapDV7ToHevc); } /** @@ -333,7 +340,8 @@ public MediaCodecVideoRenderer( @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify, - float assumedMinimumCodecOperatingRate) { + float assumedMinimumCodecOperatingRate, + boolean mapDV7ToHevc) { this( context, codecAdapterFactory, @@ -344,7 +352,8 @@ public MediaCodecVideoRenderer( eventListener, maxDroppedFramesToNotify, assumedMinimumCodecOperatingRate, - /* videoSinkProvider= */ null); + /* videoSinkProvider= */ null, + mapDV7ToHevc); } /** @@ -383,7 +392,8 @@ public MediaCodecVideoRenderer( @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify, float assumedMinimumCodecOperatingRate, - @Nullable VideoSinkProvider videoSinkProvider) { + @Nullable VideoSinkProvider videoSinkProvider, + boolean mapDV7ToHevc) { super( C.TRACK_TYPE_VIDEO, codecAdapterFactory, @@ -392,6 +402,7 @@ public MediaCodecVideoRenderer( assumedMinimumCodecOperatingRate); this.context = context.getApplicationContext(); this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; + this.mapDV7ToHevc = mapDV7ToHevc; this.videoSinkProvider = videoSinkProvider; eventDispatcher = new EventDispatcher(eventHandler, eventListener); ownsVideoSink = videoSinkProvider == null; @@ -461,7 +472,8 @@ public String getName() { mediaCodecSelector, format, requiresSecureDecryption, - /* requiresTunnelingDecoder= */ false); + /* requiresTunnelingDecoder= */ false, + mapDV7ToHevc); if (requiresSecureDecryption && decoderInfos.isEmpty()) { // No secure decoders are available. Fall back to non-secure decoders. decoderInfos = @@ -470,7 +482,8 @@ public String getName() { mediaCodecSelector, format, /* requiresSecureDecoder= */ false, - /* requiresTunnelingDecoder= */ false); + /* requiresTunnelingDecoder= */ false, + mapDV7ToHevc); } if (decoderInfos.isEmpty()) { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); @@ -524,7 +537,8 @@ public String getName() { mediaCodecSelector, format, requiresSecureDecryption, - /* requiresTunnelingDecoder= */ true); + /* requiresTunnelingDecoder= */ true, + mapDV7ToHevc); if (!tunnelingDecoderInfos.isEmpty()) { MediaCodecInfo tunnelingDecoderInfo = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(tunnelingDecoderInfos, format) @@ -549,7 +563,7 @@ protected List getDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) throws DecoderQueryException { return MediaCodecUtil.getDecoderInfosSortedByFormatSupport( - getDecoderInfos(context, mediaCodecSelector, format, requiresSecureDecoder, tunneling), + getDecoderInfos(context, mediaCodecSelector, format, requiresSecureDecoder, tunneling, mapDV7ToHevc), format); } @@ -576,7 +590,8 @@ private static List getDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder, - boolean requiresTunnelingDecoder) + boolean requiresTunnelingDecoder, + boolean mapDV7ToHevc) throws DecoderQueryException { if (format.sampleMimeType == null) { return ImmutableList.of(); @@ -586,7 +601,7 @@ private static List getDecoderInfos( && !Api26.doesDisplaySupportDolbyVision(context)) { List alternativeDecoderInfos = MediaCodecUtil.getAlternativeDecoderInfos( - mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder); + mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder, mapDV7ToHevc); if (!alternativeDecoderInfos.isEmpty()) { return alternativeDecoderInfos; } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java index 02fec74843a..c1d08147b8b 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java @@ -174,7 +174,8 @@ public void setUp() throws Exception { /* allowedJoiningTimeMs= */ 0, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1) { + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc= */ false) { @Override protected @Capabilities int supportsFormat( MediaCodecSelector mediaCodecSelector, Format format) { @@ -324,7 +325,8 @@ public void render_withBufferLimitEqualToNumberOfSamples_rendersLastFrameAfterEn /* enableDecoderFallback= */ false, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc */ false); mediaCodecVideoRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); mediaCodecVideoRenderer.enable( @@ -435,7 +437,8 @@ public void onContinueLoadingRequested(MediaPeriod source) { /* enableDecoderFallback= */ false, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc */ false); mediaCodecVideoRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); mediaCodecVideoRenderer.enable( @@ -540,7 +543,8 @@ public void onContinueLoadingRequested(MediaPeriod source) { /* enableDecoderFallback= */ false, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc */ false); mediaCodecVideoRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); mediaCodecVideoRenderer.enable( @@ -1086,7 +1090,8 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH /* allowedJoiningTimeMs= */ 0, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc= */ false); renderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); @Capabilities @@ -1171,7 +1176,8 @@ public void supportsFormat_withDolbyVision_setsDecoderSupportFlagsByDisplayDolby /* allowedJoiningTimeMs= */ 0, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc= */ false); renderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); @Capabilities int capabilitiesDvheDtr = renderer.supportsFormat(formatDvheDtr); @@ -1231,7 +1237,8 @@ public void getDecoderInfo_withNonPerformantHardwareDecoder_returnsHardwareDecod /* allowedJoiningTimeMs= */ 0, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc */ false); renderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); List mediaCodecInfoList = @@ -1274,7 +1281,8 @@ public void getDecoderInfo_softwareDecoderPreferred_returnsSoftwareDecoderFirst( /* allowedJoiningTimeMs= */ 0, /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); + /* maxDroppedFramesToNotify= */ 1, + /* mapDV7ToHevc */ false); renderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); List mediaCodecInfoList = @@ -1312,7 +1320,8 @@ public void setOutputSurface(Surface surface) { /* eventHandler= */ new Handler(testMainLooper), /* eventListener= */ eventListener, /* maxDroppedFramesToNotify= */ 1, - /* assumedMinimumCodecOperatingRate= */ 30) { + /* assumedMinimumCodecOperatingRate= */ 30, + /* mapDV7ToHevc */ false) { @Override protected @Capabilities int supportsFormat( MediaCodecSelector mediaCodecSelector, Format format) { diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java index 13f1391cae1..d1ff7b32fc4 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java @@ -206,6 +206,20 @@ private DtsHeader( 1_920, 2_048, 2_304, 2_560, 2_688, 2_816, 2_823, 2_944, 3_072, 3_840, 4_096, 6_144, 7_680 }; + /** + * Returns whether a given integer matches a DTS sync word. Synchronization and storage modes are + * defined in ETSI TS 102 114 V1.1.1 (2002-08), Section 5.3. + * + * @param word An integer. + * @return Whether a given integer matches a DTS sync word. + */ + public static boolean isSyncWord(int word) { + return word == SYNC_VALUE_BE + || word == SYNC_VALUE_LE + || word == SYNC_VALUE_14B_BE + || word == SYNC_VALUE_14B_LE; + } + /** * Maps MaxSampleRate index to sampling frequency in Hz. See ETSI TS 102 114 V1.6.1 (2019-08) * Table 7-9. diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java index f847880d3a8..2ec6fbd78d7 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java @@ -134,7 +134,8 @@ public Renderer[] createRenderers( /* enableDecoderFallback= */ false, eventHandler, videoRendererEventListener, - DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) { + DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY, + /* mapDV7ToHevc= */ false) { @Override protected boolean shouldDropOutputBuffer( long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) {