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:
parent
7b874e2dee
commit
f658fe79b1
@ -24,6 +24,7 @@ import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -112,6 +113,10 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
player.release();
|
player.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Looper getPlaybackLooper() {
|
||||||
|
return player.getPlaybackLooper();
|
||||||
|
}
|
||||||
|
|
||||||
private static final class RenderersFactoryImpl implements RenderersFactory {
|
private static final class RenderersFactoryImpl implements RenderersFactory {
|
||||||
|
|
||||||
private final TransformerMediaClock mediaClock;
|
private final TransformerMediaClock mediaClock;
|
||||||
|
@ -32,6 +32,7 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.util.Consumer;
|
import androidx.media3.common.util.Consumer;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.io.File;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@ -260,11 +261,34 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
return trackTypeToSampleCount.get(trackType, /* valueIfKeyNotFound= */ 0);
|
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() {
|
public long getDurationMs() {
|
||||||
|
if (trackTypeToTimeUs.size() == 0) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
return Util.usToMs(maxValue(trackTypeToTimeUs));
|
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.
|
* Returns whether the muxer can write a sample of the given track type.
|
||||||
*
|
*
|
||||||
|
@ -38,13 +38,15 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.audio.AudioProcessor;
|
import androidx.media3.common.audio.AudioProcessor;
|
||||||
import androidx.media3.common.util.Clock;
|
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.common.util.Util;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/* package */ final class TransformerInternal {
|
/* package */ final class TransformerInternal {
|
||||||
|
|
||||||
@ -56,8 +58,6 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@Nullable private final String outputPath;
|
|
||||||
@Nullable private final ParcelFileDescriptor outputParcelFileDescriptor;
|
|
||||||
private final TransformationRequest transformationRequest;
|
private final TransformationRequest transformationRequest;
|
||||||
private final ImmutableList<AudioProcessor> audioProcessors;
|
private final ImmutableList<AudioProcessor> audioProcessors;
|
||||||
private final ImmutableList<Effect> videoEffects;
|
private final ImmutableList<Effect> videoEffects;
|
||||||
@ -66,14 +66,18 @@ import java.util.List;
|
|||||||
private final FrameProcessor.Factory frameProcessorFactory;
|
private final FrameProcessor.Factory frameProcessorFactory;
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
private final DebugViewProvider debugViewProvider;
|
private final DebugViewProvider debugViewProvider;
|
||||||
|
private final Clock clock;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
||||||
private final MuxerWrapper muxerWrapper;
|
private final MuxerWrapper muxerWrapper;
|
||||||
private final List<SamplePipeline> samplePipelines;
|
private final List<SamplePipeline> samplePipelines;
|
||||||
|
private final ConditionVariable releasingMuxerConditionVariable;
|
||||||
|
|
||||||
private @Transformer.ProgressState int progressState;
|
private @Transformer.ProgressState int progressState;
|
||||||
private long durationMs;
|
private long durationMs;
|
||||||
private boolean released;
|
private boolean released;
|
||||||
|
private volatile @MonotonicNonNull TransformationResult transformationResult;
|
||||||
|
private volatile @MonotonicNonNull TransformationException releaseMuxerException;
|
||||||
|
|
||||||
public TransformerInternal(
|
public TransformerInternal(
|
||||||
Context context,
|
Context context,
|
||||||
@ -95,8 +99,6 @@ import java.util.List;
|
|||||||
DebugViewProvider debugViewProvider,
|
DebugViewProvider debugViewProvider,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.outputPath = outputPath;
|
|
||||||
this.outputParcelFileDescriptor = outputParcelFileDescriptor;
|
|
||||||
this.transformationRequest = transformationRequest;
|
this.transformationRequest = transformationRequest;
|
||||||
this.audioProcessors = audioProcessors;
|
this.audioProcessors = audioProcessors;
|
||||||
this.videoEffects = videoEffects;
|
this.videoEffects = videoEffects;
|
||||||
@ -105,6 +107,7 @@ import java.util.List;
|
|||||||
this.frameProcessorFactory = frameProcessorFactory;
|
this.frameProcessorFactory = frameProcessorFactory;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.debugViewProvider = debugViewProvider;
|
this.debugViewProvider = debugViewProvider;
|
||||||
|
this.clock = clock;
|
||||||
handler = Util.createHandlerForCurrentLooper();
|
handler = Util.createHandlerForCurrentLooper();
|
||||||
AssetLoaderListener assetLoaderListener = new AssetLoaderListener(mediaItem, fallbackListener);
|
AssetLoaderListener assetLoaderListener = new AssetLoaderListener(mediaItem, fallbackListener);
|
||||||
muxerWrapper =
|
muxerWrapper =
|
||||||
@ -123,6 +126,7 @@ import java.util.List;
|
|||||||
assetLoaderListener,
|
assetLoaderListener,
|
||||||
clock);
|
clock);
|
||||||
samplePipelines = new ArrayList<>(/* initialCapacity= */ 2);
|
samplePipelines = new ArrayList<>(/* initialCapacity= */ 2);
|
||||||
|
releasingMuxerConditionVariable = new ConditionVariable();
|
||||||
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,12 +157,33 @@ import java.util.List;
|
|||||||
samplePipelines.clear();
|
samplePipelines.clear();
|
||||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||||
released = true;
|
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();
|
exoPlayerAssetLoader.release();
|
||||||
try {
|
if (releaseMuxerException != null) {
|
||||||
muxerWrapper.release(forCancellation);
|
throw releaseMuxerException;
|
||||||
} catch (Muxer.MuxerException e) {
|
|
||||||
throw TransformationException.createForMuxer(
|
|
||||||
e, TransformationException.ERROR_CODE_MUXING_FAILED);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,26 +198,6 @@ import java.util.List;
|
|||||||
return positionMsSum / samplePipelines.size();
|
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 class AssetLoaderListener implements ExoPlayerAssetLoader.Listener {
|
||||||
|
|
||||||
private final MediaItem mediaItem;
|
private final MediaItem mediaItem;
|
||||||
@ -405,17 +410,7 @@ import java.util.List;
|
|||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
listener.onTransformationError(exception);
|
listener.onTransformationError(exception);
|
||||||
} else {
|
} else {
|
||||||
TransformationResult result =
|
listener.onTransformationCompleted(checkNotNull(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(getCurrentOutputFileCurrentSizeBytes())
|
|
||||||
.build();
|
|
||||||
listener.onTransformationCompleted(result);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user