mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Move audio decoding to AssetLoader
PiperOrigin-RevId: 491933937
This commit is contained in:
parent
ff7fe222b8
commit
eecf7caed0
@ -41,14 +41,14 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
|
|
||||||
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
||||||
|
|
||||||
private final Codec decoder;
|
private final DecoderInputBuffer inputBuffer;
|
||||||
private final DecoderInputBuffer decoderInputBuffer;
|
|
||||||
private final AudioProcessingPipeline audioProcessingPipeline;
|
private final AudioProcessingPipeline audioProcessingPipeline;
|
||||||
private final Codec encoder;
|
private final Codec encoder;
|
||||||
private final AudioFormat encoderInputAudioFormat;
|
private final AudioFormat encoderInputAudioFormat;
|
||||||
private final DecoderInputBuffer encoderInputBuffer;
|
private final DecoderInputBuffer encoderInputBuffer;
|
||||||
private final DecoderInputBuffer encoderOutputBuffer;
|
private final DecoderInputBuffer encoderOutputBuffer;
|
||||||
|
|
||||||
|
private boolean hasPendingInputBuffer;
|
||||||
private long nextEncoderInputBufferTimeUs;
|
private long nextEncoderInputBufferTimeUs;
|
||||||
private long encoderBufferDurationRemainder;
|
private long encoderBufferDurationRemainder;
|
||||||
|
|
||||||
@ -58,7 +58,6 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
ImmutableList<AudioProcessor> audioProcessors,
|
ImmutableList<AudioProcessor> audioProcessors,
|
||||||
Codec.DecoderFactory decoderFactory,
|
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
MuxerWrapper muxerWrapper,
|
MuxerWrapper muxerWrapper,
|
||||||
Listener listener,
|
Listener listener,
|
||||||
@ -72,12 +71,10 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
muxerWrapper,
|
muxerWrapper,
|
||||||
listener);
|
listener);
|
||||||
|
|
||||||
decoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
inputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
|
|
||||||
decoder = decoderFactory.createForAudioDecoding(inputFormat);
|
|
||||||
|
|
||||||
if (transformationRequest.flattenForSlowMotion) {
|
if (transformationRequest.flattenForSlowMotion) {
|
||||||
audioProcessors =
|
audioProcessors =
|
||||||
new ImmutableList.Builder<AudioProcessor>()
|
new ImmutableList.Builder<AudioProcessor>()
|
||||||
@ -139,30 +136,34 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
nextEncoderInputBufferTimeUs = streamOffsetUs;
|
nextEncoderInputBufferTimeUs = streamOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean expectsDecodedData() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
audioProcessingPipeline.reset();
|
audioProcessingPipeline.reset();
|
||||||
decoder.release();
|
|
||||||
encoder.release();
|
encoder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
protected DecoderInputBuffer dequeueInputBufferInternal() throws TransformationException {
|
protected DecoderInputBuffer dequeueInputBufferInternal() {
|
||||||
return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null;
|
return hasPendingInputBuffer ? null : inputBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void queueInputBufferInternal() throws TransformationException {
|
protected void queueInputBufferInternal() {
|
||||||
decoder.queueInputBuffer(decoderInputBuffer);
|
hasPendingInputBuffer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean processDataUpToMuxer() throws TransformationException {
|
protected boolean processDataUpToMuxer() throws TransformationException {
|
||||||
if (audioProcessingPipeline.isOperational()) {
|
if (audioProcessingPipeline.isOperational()) {
|
||||||
return feedEncoderFromProcessingPipeline() || feedProcessingPipelineFromDecoder();
|
return feedEncoderFromProcessingPipeline() || feedProcessingPipelineFromInput();
|
||||||
} else {
|
} else {
|
||||||
return feedEncoderFromDecoder();
|
return feedEncoderFromInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,27 +196,25 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
|
* Attempts to pass input data to the encoder.
|
||||||
* pass more data immediately by calling this method again.
|
*
|
||||||
|
* @return Whether it may be possible to feed more data immediately by calling this method again.
|
||||||
*/
|
*/
|
||||||
private boolean feedEncoderFromDecoder() throws TransformationException {
|
private boolean feedEncoderFromInput() throws TransformationException {
|
||||||
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
if (!hasPendingInputBuffer || !encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decoder.isEnded()) {
|
if (inputBuffer.isEndOfStream()) {
|
||||||
queueEndOfStreamToEncoder();
|
queueEndOfStreamToEncoder();
|
||||||
|
hasPendingInputBuffer = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable ByteBuffer decoderOutputBuffer = decoder.getOutputBuffer();
|
ByteBuffer inputData = checkNotNull(inputBuffer.data);
|
||||||
if (decoderOutputBuffer == null) {
|
feedEncoder(inputData);
|
||||||
return false;
|
if (!inputData.hasRemaining()) {
|
||||||
}
|
hasPendingInputBuffer = false;
|
||||||
|
|
||||||
feedEncoder(decoderOutputBuffer);
|
|
||||||
if (!decoderOutputBuffer.hasRemaining()) {
|
|
||||||
decoder.releaseOutputBuffer(/* render= */ false);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -223,7 +222,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
/**
|
/**
|
||||||
* Attempts to feed audio processor output data to the encoder.
|
* Attempts to feed audio processor output data to the encoder.
|
||||||
*
|
*
|
||||||
* @return Whether more data can be fed immediately, by calling this method again.
|
* @return Whether it may be possible to feed more data immediately by calling this method again.
|
||||||
*/
|
*/
|
||||||
private boolean feedEncoderFromProcessingPipeline() throws TransformationException {
|
private boolean feedEncoderFromProcessingPipeline() throws TransformationException {
|
||||||
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
||||||
@ -244,28 +243,28 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to feed decoder output data to the {@link AudioProcessingPipeline}.
|
* Attempts to feed input data to the {@link AudioProcessingPipeline}.
|
||||||
*
|
*
|
||||||
* @return Whether it may be possible to feed more data immediately by calling this method again.
|
* @return Whether it may be possible to feed more data immediately by calling this method again.
|
||||||
*/
|
*/
|
||||||
private boolean feedProcessingPipelineFromDecoder() throws TransformationException {
|
private boolean feedProcessingPipelineFromInput() {
|
||||||
if (decoder.isEnded()) {
|
if (!hasPendingInputBuffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputBuffer.isEndOfStream()) {
|
||||||
audioProcessingPipeline.queueEndOfStream();
|
audioProcessingPipeline.queueEndOfStream();
|
||||||
|
hasPendingInputBuffer = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
checkState(!audioProcessingPipeline.isEnded());
|
checkState(!audioProcessingPipeline.isEnded());
|
||||||
|
|
||||||
@Nullable ByteBuffer decoderOutputBuffer = decoder.getOutputBuffer();
|
ByteBuffer inputData = checkNotNull(inputBuffer.data);
|
||||||
if (decoderOutputBuffer == null) {
|
audioProcessingPipeline.queueInput(inputData);
|
||||||
|
if (inputData.hasRemaining()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
hasPendingInputBuffer = false;
|
||||||
audioProcessingPipeline.queueInput(decoderOutputBuffer);
|
|
||||||
if (decoderOutputBuffer.hasRemaining()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Decoder output buffer was fully consumed by the processing pipeline.
|
|
||||||
decoder.releaseOutputBuffer(/* render= */ false);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
boolean removeAudio,
|
boolean removeAudio,
|
||||||
boolean removeVideo,
|
boolean removeVideo,
|
||||||
MediaSource.Factory mediaSourceFactory,
|
MediaSource.Factory mediaSourceFactory,
|
||||||
|
Codec.DecoderFactory decoderFactory,
|
||||||
Looper looper,
|
Looper looper,
|
||||||
Listener listener,
|
Listener listener,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
@ -89,7 +90,9 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10)
|
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10)
|
||||||
.build();
|
.build();
|
||||||
ExoPlayer.Builder playerBuilder =
|
ExoPlayer.Builder playerBuilder =
|
||||||
new ExoPlayer.Builder(context, new RenderersFactoryImpl(removeAudio, removeVideo, listener))
|
new ExoPlayer.Builder(
|
||||||
|
context,
|
||||||
|
new RenderersFactoryImpl(removeAudio, removeVideo, decoderFactory, listener))
|
||||||
.setMediaSourceFactory(mediaSourceFactory)
|
.setMediaSourceFactory(mediaSourceFactory)
|
||||||
.setTrackSelector(trackSelector)
|
.setTrackSelector(trackSelector)
|
||||||
.setLoadControl(loadControl)
|
.setLoadControl(loadControl)
|
||||||
@ -120,14 +123,17 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
private final TransformerMediaClock mediaClock;
|
private final TransformerMediaClock mediaClock;
|
||||||
private final boolean removeAudio;
|
private final boolean removeAudio;
|
||||||
private final boolean removeVideo;
|
private final boolean removeVideo;
|
||||||
|
private final Codec.DecoderFactory decoderFactory;
|
||||||
private final ExoPlayerAssetLoader.Listener assetLoaderListener;
|
private final ExoPlayerAssetLoader.Listener assetLoaderListener;
|
||||||
|
|
||||||
public RenderersFactoryImpl(
|
public RenderersFactoryImpl(
|
||||||
boolean removeAudio,
|
boolean removeAudio,
|
||||||
boolean removeVideo,
|
boolean removeVideo,
|
||||||
|
Codec.DecoderFactory decoderFactory,
|
||||||
ExoPlayerAssetLoader.Listener assetLoaderListener) {
|
ExoPlayerAssetLoader.Listener assetLoaderListener) {
|
||||||
this.removeAudio = removeAudio;
|
this.removeAudio = removeAudio;
|
||||||
this.removeVideo = removeVideo;
|
this.removeVideo = removeVideo;
|
||||||
|
this.decoderFactory = decoderFactory;
|
||||||
this.assetLoaderListener = assetLoaderListener;
|
this.assetLoaderListener = assetLoaderListener;
|
||||||
mediaClock = new TransformerMediaClock();
|
mediaClock = new TransformerMediaClock();
|
||||||
}
|
}
|
||||||
@ -144,12 +150,14 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
if (!removeAudio) {
|
if (!removeAudio) {
|
||||||
renderers[index] =
|
renderers[index] =
|
||||||
new ExoPlayerAssetLoaderRenderer(C.TRACK_TYPE_AUDIO, mediaClock, assetLoaderListener);
|
new ExoPlayerAssetLoaderRenderer(
|
||||||
|
C.TRACK_TYPE_AUDIO, decoderFactory, mediaClock, assetLoaderListener);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if (!removeVideo) {
|
if (!removeVideo) {
|
||||||
renderers[index] =
|
renderers[index] =
|
||||||
new ExoPlayerAssetLoaderRenderer(C.TRACK_TYPE_VIDEO, mediaClock, assetLoaderListener);
|
new ExoPlayerAssetLoaderRenderer(
|
||||||
|
C.TRACK_TYPE_VIDEO, decoderFactory, mediaClock, assetLoaderListener);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
return renderers;
|
return renderers;
|
||||||
|
@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
|||||||
import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED;
|
import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED;
|
||||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
@ -30,6 +31,7 @@ import androidx.media3.exoplayer.FormatHolder;
|
|||||||
import androidx.media3.exoplayer.MediaClock;
|
import androidx.media3.exoplayer.MediaClock;
|
||||||
import androidx.media3.exoplayer.RendererCapabilities;
|
import androidx.media3.exoplayer.RendererCapabilities;
|
||||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
@ -38,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
private static final String TAG = "ExoPlayerAssetLoaderRenderer";
|
private static final String TAG = "ExoPlayerAssetLoaderRenderer";
|
||||||
|
|
||||||
|
private final Codec.DecoderFactory decoderFactory;
|
||||||
private final TransformerMediaClock mediaClock;
|
private final TransformerMediaClock mediaClock;
|
||||||
private final ExoPlayerAssetLoader.Listener assetLoaderListener;
|
private final ExoPlayerAssetLoader.Listener assetLoaderListener;
|
||||||
private final DecoderInputBuffer decoderInputBuffer;
|
private final DecoderInputBuffer decoderInputBuffer;
|
||||||
@ -45,14 +48,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private boolean isTransformationRunning;
|
private boolean isTransformationRunning;
|
||||||
private long streamStartPositionUs;
|
private long streamStartPositionUs;
|
||||||
private long streamOffsetUs;
|
private long streamOffsetUs;
|
||||||
|
private @MonotonicNonNull Codec decoder;
|
||||||
|
@Nullable private ByteBuffer pendingDecoderOutputBuffer;
|
||||||
private SamplePipeline.@MonotonicNonNull Input samplePipelineInput;
|
private SamplePipeline.@MonotonicNonNull Input samplePipelineInput;
|
||||||
private boolean isEnded;
|
private boolean isEnded;
|
||||||
|
|
||||||
public ExoPlayerAssetLoaderRenderer(
|
public ExoPlayerAssetLoaderRenderer(
|
||||||
int trackType,
|
int trackType,
|
||||||
|
Codec.DecoderFactory decoderFactory,
|
||||||
TransformerMediaClock mediaClock,
|
TransformerMediaClock mediaClock,
|
||||||
ExoPlayerAssetLoader.Listener assetLoaderListener) {
|
ExoPlayerAssetLoader.Listener assetLoaderListener) {
|
||||||
super(trackType);
|
super(trackType);
|
||||||
|
this.decoderFactory = decoderFactory;
|
||||||
this.mediaClock = mediaClock;
|
this.mediaClock = mediaClock;
|
||||||
this.assetLoaderListener = assetLoaderListener;
|
this.assetLoaderListener = assetLoaderListener;
|
||||||
decoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
decoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
@ -99,7 +106,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (feedPipelineFromInput()) {}
|
if (samplePipelineInput.expectsDecodedData()) {
|
||||||
|
while (feedPipelineFromDecoder() || feedDecoderFromInput()) {}
|
||||||
|
} else {
|
||||||
|
while (feedPipelineFromInput()) {}
|
||||||
|
}
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
isTransformationRunning = false;
|
isTransformationRunning = false;
|
||||||
assetLoaderListener.onError(e);
|
assetLoaderListener.onError(e);
|
||||||
@ -128,6 +139,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
isTransformationRunning = false;
|
isTransformationRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
if (decoder != null) {
|
||||||
|
decoder.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EnsuresNonNullIf(expression = "samplePipelineInput", result = true)
|
@EnsuresNonNullIf(expression = "samplePipelineInput", result = true)
|
||||||
private boolean ensureConfigured() throws TransformationException {
|
private boolean ensureConfigured() throws TransformationException {
|
||||||
if (samplePipelineInput != null) {
|
if (samplePipelineInput != null) {
|
||||||
@ -143,6 +161,74 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
Format inputFormat = checkNotNull(formatHolder.format);
|
Format inputFormat = checkNotNull(formatHolder.format);
|
||||||
samplePipelineInput =
|
samplePipelineInput =
|
||||||
assetLoaderListener.onTrackAdded(inputFormat, streamStartPositionUs, streamOffsetUs);
|
assetLoaderListener.onTrackAdded(inputFormat, streamStartPositionUs, streamOffsetUs);
|
||||||
|
if (samplePipelineInput.expectsDecodedData()) {
|
||||||
|
decoder = decoderFactory.createForAudioDecoding(inputFormat);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to read decoded data and pass it to the sample pipeline.
|
||||||
|
*
|
||||||
|
* @return Whether it may be possible to read more data immediately by calling this method again.
|
||||||
|
* @throws TransformationException If an error occurs in the decoder or in the {@link
|
||||||
|
* SamplePipeline}.
|
||||||
|
*/
|
||||||
|
@RequiresNonNull("samplePipelineInput")
|
||||||
|
private boolean feedPipelineFromDecoder() throws TransformationException {
|
||||||
|
@Nullable
|
||||||
|
DecoderInputBuffer samplePipelineInputBuffer = samplePipelineInput.dequeueInputBuffer();
|
||||||
|
if (samplePipelineInputBuffer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Codec decoder = checkNotNull(this.decoder);
|
||||||
|
if (pendingDecoderOutputBuffer != null) {
|
||||||
|
if (pendingDecoderOutputBuffer.hasRemaining()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
decoder.releaseOutputBuffer(/* render= */ false);
|
||||||
|
pendingDecoderOutputBuffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder.isEnded()) {
|
||||||
|
samplePipelineInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||||
|
samplePipelineInput.queueInputBuffer();
|
||||||
|
isEnded = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingDecoderOutputBuffer = decoder.getOutputBuffer();
|
||||||
|
if (pendingDecoderOutputBuffer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePipelineInputBuffer.data = pendingDecoderOutputBuffer;
|
||||||
|
MediaCodec.BufferInfo bufferInfo = checkNotNull(decoder.getOutputBufferInfo());
|
||||||
|
samplePipelineInputBuffer.timeUs = bufferInfo.presentationTimeUs;
|
||||||
|
samplePipelineInputBuffer.setFlags(bufferInfo.flags);
|
||||||
|
samplePipelineInput.queueInputBuffer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to read input data and pass it to the decoder.
|
||||||
|
*
|
||||||
|
* @return Whether it may be possible to read more data immediately by calling this method again.
|
||||||
|
* @throws TransformationException If an error occurs in the decoder.
|
||||||
|
*/
|
||||||
|
private boolean feedDecoderFromInput() throws TransformationException {
|
||||||
|
Codec decoder = checkNotNull(this.decoder);
|
||||||
|
if (!decoder.maybeDequeueInputBuffer(decoderInputBuffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readInput(decoderInputBuffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.queueInputBuffer(decoderInputBuffer);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,18 +245,32 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadDataResult
|
if (!readInput(samplePipelineInputBuffer)) {
|
||||||
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePipelineInput.queueInputBuffer();
|
||||||
|
if (samplePipelineInputBuffer.isEndOfStream()) {
|
||||||
|
isEnded = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to populate {@code buffer} with input data.
|
||||||
|
*
|
||||||
|
* @param buffer The buffer to populate.
|
||||||
|
* @return Whether the {@code buffer} has been populated.
|
||||||
|
*/
|
||||||
|
private boolean readInput(DecoderInputBuffer buffer) {
|
||||||
|
@ReadDataResult int result = readSource(getFormatHolder(), buffer, /* readFlags= */ 0);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case C.RESULT_BUFFER_READ:
|
case C.RESULT_BUFFER_READ:
|
||||||
samplePipelineInputBuffer.flip();
|
buffer.flip();
|
||||||
if (samplePipelineInputBuffer.isEndOfStream()) {
|
if (!buffer.isEndOfStream()) {
|
||||||
samplePipelineInput.queueInputBuffer();
|
mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs);
|
||||||
isEnded = true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
|
|
||||||
samplePipelineInput.queueInputBuffer();
|
|
||||||
return true;
|
return true;
|
||||||
case C.RESULT_FORMAT_READ:
|
case C.RESULT_FORMAT_READ:
|
||||||
throw new IllegalStateException("Format changes are not supported.");
|
throw new IllegalStateException("Format changes are not supported.");
|
||||||
|
@ -48,6 +48,11 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
|||||||
fallbackListener.onTransformationRequestFinalized(transformationRequest);
|
fallbackListener.onTransformationRequestFinalized(transformationRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean expectsDecodedData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {}
|
public void release() {}
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
|||||||
/** Input of a {@link SamplePipeline}. */
|
/** Input of a {@link SamplePipeline}. */
|
||||||
interface Input {
|
interface Input {
|
||||||
|
|
||||||
|
/** See {@link SamplePipeline#expectsDecodedData()}. */
|
||||||
|
boolean expectsDecodedData();
|
||||||
|
|
||||||
/** See {@link SamplePipeline#dequeueInputBuffer()}. */
|
/** See {@link SamplePipeline#dequeueInputBuffer()}. */
|
||||||
@Nullable
|
@Nullable
|
||||||
DecoderInputBuffer dequeueInputBuffer();
|
DecoderInputBuffer dequeueInputBuffer();
|
||||||
@ -56,6 +59,12 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
|||||||
void onTransformationError(TransformationException exception);
|
void onTransformationError(TransformationException exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the pipeline should be fed with decoded sample data. If false, encoded sample
|
||||||
|
* data should be queued.
|
||||||
|
*/
|
||||||
|
boolean expectsDecodedData();
|
||||||
|
|
||||||
/** Returns a buffer if the pipeline is ready to accept input, and {@code null} otherwise. */
|
/** Returns a buffer if the pipeline is ready to accept input, and {@code null} otherwise. */
|
||||||
@Nullable
|
@Nullable
|
||||||
DecoderInputBuffer dequeueInputBuffer() throws TransformationException;
|
DecoderInputBuffer dequeueInputBuffer() throws TransformationException;
|
||||||
|
@ -163,6 +163,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
removeAudio,
|
removeAudio,
|
||||||
removeVideo,
|
removeVideo,
|
||||||
mediaSourceFactory,
|
mediaSourceFactory,
|
||||||
|
decoderFactory,
|
||||||
internalLooper,
|
internalLooper,
|
||||||
componentListener,
|
componentListener,
|
||||||
clock);
|
clock);
|
||||||
@ -409,7 +410,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
int samplePipelineIndex = tracksAddedCount;
|
int samplePipelineIndex = tracksAddedCount;
|
||||||
tracksAddedCount++;
|
tracksAddedCount++;
|
||||||
return new SamplePipelineInput(samplePipelineIndex);
|
return new SamplePipelineInput(samplePipelineIndex, samplePipeline.expectsDecodedData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -458,7 +459,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
audioProcessors,
|
audioProcessors,
|
||||||
decoderFactory,
|
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
muxerWrapper,
|
muxerWrapper,
|
||||||
/* listener= */ this,
|
/* listener= */ this,
|
||||||
@ -573,9 +573,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private class SamplePipelineInput implements SamplePipeline.Input {
|
private class SamplePipelineInput implements SamplePipeline.Input {
|
||||||
|
|
||||||
private final int samplePipelineIndex;
|
private final int samplePipelineIndex;
|
||||||
|
private final boolean expectsDecodedData;
|
||||||
|
|
||||||
public SamplePipelineInput(int samplePipelineIndex) {
|
public SamplePipelineInput(int samplePipelineIndex, boolean expectsDecodedData) {
|
||||||
this.samplePipelineIndex = samplePipelineIndex;
|
this.samplePipelineIndex = samplePipelineIndex;
|
||||||
|
this.expectsDecodedData = expectsDecodedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean expectsDecodedData() {
|
||||||
|
return expectsDecodedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -207,6 +207,11 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
maxPendingFrameCount = decoder.getMaxPendingFrameCount();
|
maxPendingFrameCount = decoder.getMaxPendingFrameCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean expectsDecodedData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
frameProcessor.release();
|
frameProcessor.release();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user