Make sure Muxer is always accessed from playback thread

This thread will become the Transformer internal thread.

PiperOrigin-RevId: 489168435
This commit is contained in:
kimvde 2022-11-17 11:19:49 +00:00 committed by microkatz
parent 7b874e2dee
commit f658fe79b1
3 changed files with 66 additions and 42 deletions

View File

@ -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;

View File

@ -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.
*

View File

@ -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<AudioProcessor> audioProcessors;
private final ImmutableList<Effect> 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<SamplePipeline> 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));
}
});
}