From f658fe79b195f131dc442e3a96e1833539ff9d26 Mon Sep 17 00:00:00 2001 From: kimvde Date: Thu, 17 Nov 2022 11:19:49 +0000 Subject: [PATCH] Make sure Muxer is always accessed from playback thread This thread will become the Transformer internal thread. PiperOrigin-RevId: 489168435 --- .../transformer/ExoPlayerAssetLoader.java | 5 ++ .../media3/transformer/MuxerWrapper.java | 26 ++++++- .../transformer/TransformerInternal.java | 77 +++++++++---------- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java index e348ea0e3e..100431e077 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java @@ -24,6 +24,7 @@ import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS import android.content.Context; import android.os.Handler; +import android.os.Looper; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; @@ -112,6 +113,10 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; player.release(); } + public Looper getPlaybackLooper() { + return player.getPlaybackLooper(); + } + private static final class RenderersFactoryImpl implements RenderersFactory { private final TransformerMediaClock mediaClock; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java index d85626c3ba..91a4ddb58c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java @@ -32,6 +32,7 @@ import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Consumer; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; +import java.io.File; import java.nio.ByteBuffer; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -260,11 +261,34 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return trackTypeToSampleCount.get(trackType, /* valueIfKeyNotFound= */ 0); } - /** Returns the duration of the longest track in milliseconds. */ + /** + * Returns the duration of the longest track in milliseconds, or {@link C#TIME_UNSET} if there is + * no track. + */ public long getDurationMs() { + if (trackTypeToTimeUs.size() == 0) { + return C.TIME_UNSET; + } return Util.usToMs(maxValue(trackTypeToTimeUs)); } + /** Returns the current size in bytes of the output, or {@link C#LENGTH_UNSET} if unavailable. */ + public long getCurrentOutputSizeBytes() { + long fileSize = C.LENGTH_UNSET; + + if (outputPath != null) { + fileSize = new File(outputPath).length(); + } else if (outputParcelFileDescriptor != null) { + fileSize = outputParcelFileDescriptor.getStatSize(); + } + + if (fileSize <= 0) { + fileSize = C.LENGTH_UNSET; + } + + return fileSize; + } + /** * Returns whether the muxer can write a sample of the given track type. * diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java index f0a3c84c09..22ad92c830 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -38,13 +38,15 @@ import androidx.media3.common.MimeTypes; import androidx.media3.common.PlaybackException; import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.util.Clock; +import androidx.media3.common.util.ConditionVariable; +import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.extractor.metadata.mp4.SlowMotionData; import com.google.common.collect.ImmutableList; -import java.io.File; import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* package */ final class TransformerInternal { @@ -56,8 +58,6 @@ import java.util.List; } private final Context context; - @Nullable private final String outputPath; - @Nullable private final ParcelFileDescriptor outputParcelFileDescriptor; private final TransformationRequest transformationRequest; private final ImmutableList audioProcessors; private final ImmutableList videoEffects; @@ -66,14 +66,18 @@ import java.util.List; private final FrameProcessor.Factory frameProcessorFactory; private final Listener listener; private final DebugViewProvider debugViewProvider; + private final Clock clock; private final Handler handler; private final ExoPlayerAssetLoader exoPlayerAssetLoader; private final MuxerWrapper muxerWrapper; private final List samplePipelines; + private final ConditionVariable releasingMuxerConditionVariable; private @Transformer.ProgressState int progressState; private long durationMs; private boolean released; + private volatile @MonotonicNonNull TransformationResult transformationResult; + private volatile @MonotonicNonNull TransformationException releaseMuxerException; public TransformerInternal( Context context, @@ -95,8 +99,6 @@ import java.util.List; DebugViewProvider debugViewProvider, Clock clock) { this.context = context; - this.outputPath = outputPath; - this.outputParcelFileDescriptor = outputParcelFileDescriptor; this.transformationRequest = transformationRequest; this.audioProcessors = audioProcessors; this.videoEffects = videoEffects; @@ -105,6 +107,7 @@ import java.util.List; this.frameProcessorFactory = frameProcessorFactory; this.listener = listener; this.debugViewProvider = debugViewProvider; + this.clock = clock; handler = Util.createHandlerForCurrentLooper(); AssetLoaderListener assetLoaderListener = new AssetLoaderListener(mediaItem, fallbackListener); muxerWrapper = @@ -123,6 +126,7 @@ import java.util.List; assetLoaderListener, clock); samplePipelines = new ArrayList<>(/* initialCapacity= */ 2); + releasingMuxerConditionVariable = new ConditionVariable(); progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; } @@ -153,12 +157,33 @@ import java.util.List; samplePipelines.clear(); progressState = PROGRESS_STATE_NO_TRANSFORMATION; released = true; + HandlerWrapper playbackHandler = + clock.createHandler(exoPlayerAssetLoader.getPlaybackLooper(), /* callback= */ null); + playbackHandler.post( + () -> { + transformationResult = + new TransformationResult.Builder() + .setDurationMs(checkNotNull(muxerWrapper).getDurationMs()) + .setAverageAudioBitrate(muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_AUDIO)) + .setAverageVideoBitrate(muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_VIDEO)) + .setVideoFrameCount(muxerWrapper.getTrackSampleCount(C.TRACK_TYPE_VIDEO)) + .setFileSizeBytes(muxerWrapper.getCurrentOutputSizeBytes()) + .build(); + try { + muxerWrapper.release(forCancellation); + } catch (Muxer.MuxerException e) { + releaseMuxerException = + TransformationException.createForMuxer( + e, TransformationException.ERROR_CODE_MUXING_FAILED); + } finally { + releasingMuxerConditionVariable.open(); + } + }); + clock.onThreadBlocked(); + releasingMuxerConditionVariable.blockUninterruptible(); exoPlayerAssetLoader.release(); - try { - muxerWrapper.release(forCancellation); - } catch (Muxer.MuxerException e) { - throw TransformationException.createForMuxer( - e, TransformationException.ERROR_CODE_MUXING_FAILED); + if (releaseMuxerException != null) { + throw releaseMuxerException; } } @@ -173,26 +198,6 @@ import java.util.List; return positionMsSum / samplePipelines.size(); } - /** - * Returns the current size in bytes of the current/latest output file, or {@link C#LENGTH_UNSET} - * if unavailable. - */ - private long getCurrentOutputFileCurrentSizeBytes() { - long fileSize = C.LENGTH_UNSET; - - if (outputPath != null) { - fileSize = new File(outputPath).length(); - } else if (outputParcelFileDescriptor != null) { - fileSize = outputParcelFileDescriptor.getStatSize(); - } - - if (fileSize <= 0) { - fileSize = C.LENGTH_UNSET; - } - - return fileSize; - } - private class AssetLoaderListener implements ExoPlayerAssetLoader.Listener { private final MediaItem mediaItem; @@ -405,17 +410,7 @@ import java.util.List; if (exception != null) { listener.onTransformationError(exception); } else { - TransformationResult result = - new TransformationResult.Builder() - .setDurationMs(checkNotNull(muxerWrapper).getDurationMs()) - .setAverageAudioBitrate( - muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_AUDIO)) - .setAverageVideoBitrate( - muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_VIDEO)) - .setVideoFrameCount(muxerWrapper.getTrackSampleCount(C.TRACK_TYPE_VIDEO)) - .setFileSizeBytes(getCurrentOutputFileCurrentSizeBytes()) - .build(); - listener.onTransformationCompleted(result); + listener.onTransformationCompleted(checkNotNull(transformationResult)); } }); }