mirror of
https://github.com/androidx/media.git
synced 2025-05-05 14:40:50 +08:00
Move muxing inside sample pipelines
This logic is currently in the player renderers. With multi-asset, the renderers will go into the AssetLoader, which shouldn't be responsible for muxing. PiperOrigin-RevId: 486860502
This commit is contained in:
parent
b10b4e6d46
commit
d8754b6642
@ -28,14 +28,13 @@ import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
|
|||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
import org.checkerframework.dataflow.qual.Pure;
|
import org.checkerframework.dataflow.qual.Pure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pipeline to decode audio samples, apply transformations on the raw samples, and re-encode them.
|
* Pipeline to decode audio samples, apply transformations on the raw samples, and re-encode them.
|
||||||
*/
|
*/
|
||||||
/* package */ final class AudioTranscodingSamplePipeline implements SamplePipeline {
|
/* package */ final class AudioTranscodingSamplePipeline extends BaseSamplePipeline {
|
||||||
|
|
||||||
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
||||||
|
|
||||||
@ -57,12 +56,15 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
public AudioTranscodingSamplePipeline(
|
public AudioTranscodingSamplePipeline(
|
||||||
Format inputFormat,
|
Format inputFormat,
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
|
long streamStartPositionUs,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
Codec.DecoderFactory decoderFactory,
|
Codec.DecoderFactory decoderFactory,
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
List<String> allowedOutputMimeTypes,
|
MuxerWrapper muxerWrapper,
|
||||||
FallbackListener fallbackListener)
|
FallbackListener fallbackListener)
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
|
super(C.TRACK_TYPE_AUDIO, streamStartPositionUs, muxerWrapper);
|
||||||
|
|
||||||
decoderInputBuffer =
|
decoderInputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderInputBuffer =
|
encoderInputBuffer =
|
||||||
@ -104,7 +106,9 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
.setChannelCount(encoderInputAudioFormat.channelCount)
|
.setChannelCount(encoderInputAudioFormat.channelCount)
|
||||||
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
||||||
.build();
|
.build();
|
||||||
encoder = encoderFactory.createForAudioEncoding(requestedOutputFormat, allowedOutputMimeTypes);
|
encoder =
|
||||||
|
encoderFactory.createForAudioEncoding(
|
||||||
|
requestedOutputFormat, muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO));
|
||||||
|
|
||||||
fallbackListener.onTransformationRequestFinalized(
|
fallbackListener.onTransformationRequestFinalized(
|
||||||
createFallbackTransformationRequest(
|
createFallbackTransformationRequest(
|
||||||
@ -126,7 +130,16 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processData() throws TransformationException {
|
public void release() {
|
||||||
|
if (speedChangingAudioProcessor != null) {
|
||||||
|
speedChangingAudioProcessor.reset();
|
||||||
|
}
|
||||||
|
decoder.release();
|
||||||
|
encoder.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean processDataUpToMuxer() throws TransformationException {
|
||||||
if (speedChangingAudioProcessor != null) {
|
if (speedChangingAudioProcessor != null) {
|
||||||
return feedEncoderFromProcessor() || feedProcessorFromDecoder();
|
return feedEncoderFromProcessor() || feedProcessorFromDecoder();
|
||||||
} else {
|
} else {
|
||||||
@ -136,13 +149,13 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Format getOutputFormat() throws TransformationException {
|
protected Format getMuxerInputFormat() throws TransformationException {
|
||||||
return encoder.getOutputFormat();
|
return encoder.getOutputFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DecoderInputBuffer getOutputBuffer() throws TransformationException {
|
protected DecoderInputBuffer getMuxerInputBuffer() throws TransformationException {
|
||||||
encoderOutputBuffer.data = encoder.getOutputBuffer();
|
encoderOutputBuffer.data = encoder.getOutputBuffer();
|
||||||
if (encoderOutputBuffer.data == null) {
|
if (encoderOutputBuffer.data == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -153,24 +166,15 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseOutputBuffer() throws TransformationException {
|
protected void releaseMuxerInputBuffer() throws TransformationException {
|
||||||
encoder.releaseOutputBuffer(/* render= */ false);
|
encoder.releaseOutputBuffer(/* render= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
protected boolean isMuxerInputEnded() {
|
||||||
return encoder.isEnded();
|
return encoder.isEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
if (speedChangingAudioProcessor != null) {
|
|
||||||
speedChangingAudioProcessor.reset();
|
|
||||||
}
|
|
||||||
decoder.release();
|
|
||||||
encoder.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
|
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
|
||||||
* pass more data immediately by calling this method again.
|
* pass more data immediately by calling this method again.
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.transformer;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
|
|
||||||
|
/* package */ abstract class BaseSamplePipeline implements SamplePipeline {
|
||||||
|
|
||||||
|
private final int trackType;
|
||||||
|
private final long streamStartPositionUs;
|
||||||
|
private final MuxerWrapper muxerWrapper;
|
||||||
|
|
||||||
|
private boolean muxerWrapperTrackAdded;
|
||||||
|
private boolean isEnded;
|
||||||
|
|
||||||
|
public BaseSamplePipeline(int trackType, long streamStartPositionUs, MuxerWrapper muxerWrapper) {
|
||||||
|
this.trackType = trackType;
|
||||||
|
this.streamStartPositionUs = streamStartPositionUs;
|
||||||
|
this.muxerWrapper = muxerWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processData() throws TransformationException {
|
||||||
|
return feedMuxer() || processDataUpToMuxer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnded() {
|
||||||
|
return isEnded;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean processDataUpToMuxer() throws TransformationException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected abstract Format getMuxerInputFormat() throws TransformationException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected abstract DecoderInputBuffer getMuxerInputBuffer() throws TransformationException;
|
||||||
|
|
||||||
|
protected abstract void releaseMuxerInputBuffer() throws TransformationException;
|
||||||
|
|
||||||
|
protected abstract boolean isMuxerInputEnded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to pass encoded data to the muxer, and returns whether it may be possible to pass more
|
||||||
|
* data immediately by calling this method again.
|
||||||
|
*/
|
||||||
|
private boolean feedMuxer() throws TransformationException {
|
||||||
|
if (!muxerWrapperTrackAdded) {
|
||||||
|
@Nullable Format inputFormat = getMuxerInputFormat();
|
||||||
|
if (inputFormat == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
muxerWrapper.addTrackFormat(inputFormat);
|
||||||
|
} catch (Muxer.MuxerException e) {
|
||||||
|
throw TransformationException.createForMuxer(
|
||||||
|
e, TransformationException.ERROR_CODE_MUXING_FAILED);
|
||||||
|
}
|
||||||
|
muxerWrapperTrackAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMuxerInputEnded()) {
|
||||||
|
muxerWrapper.endTrack(trackType);
|
||||||
|
isEnded = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable DecoderInputBuffer muxerInputBuffer = getMuxerInputBuffer();
|
||||||
|
if (muxerInputBuffer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long samplePresentationTimeUs = muxerInputBuffer.timeUs - streamStartPositionUs;
|
||||||
|
// TODO(b/204892224): Consider subtracting the first sample timestamp from the sample pipeline
|
||||||
|
// buffer from all samples so that they are guaranteed to start from zero in the output file.
|
||||||
|
try {
|
||||||
|
if (!muxerWrapper.writeSample(
|
||||||
|
trackType,
|
||||||
|
checkStateNotNull(muxerInputBuffer.data),
|
||||||
|
muxerInputBuffer.isKeyFrame(),
|
||||||
|
samplePresentationTimeUs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Muxer.MuxerException e) {
|
||||||
|
throw TransformationException.createForMuxer(
|
||||||
|
e, TransformationException.ERROR_CODE_MUXING_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseMuxerInputBuffer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -19,9 +19,10 @@ package com.google.android.exoplayer2.transformer;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
|
||||||
/** Pipeline that passes through the samples without any re-encoding or transformation. */
|
/** Pipeline that passes through the samples without any re-encoding or transformation. */
|
||||||
/* package */ final class PassthroughSamplePipeline implements SamplePipeline {
|
/* package */ final class PassthroughSamplePipeline extends BaseSamplePipeline {
|
||||||
|
|
||||||
private final DecoderInputBuffer buffer;
|
private final DecoderInputBuffer buffer;
|
||||||
private final Format format;
|
private final Format format;
|
||||||
@ -30,8 +31,11 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
|||||||
|
|
||||||
public PassthroughSamplePipeline(
|
public PassthroughSamplePipeline(
|
||||||
Format format,
|
Format format,
|
||||||
|
long streamStartPositionUs,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
|
MuxerWrapper muxerWrapper,
|
||||||
FallbackListener fallbackListener) {
|
FallbackListener fallbackListener) {
|
||||||
|
super(MimeTypes.getTrackType(format.sampleMimeType), streamStartPositionUs, muxerWrapper);
|
||||||
this.format = format;
|
this.format = format;
|
||||||
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||||
hasPendingBuffer = false;
|
hasPendingBuffer = false;
|
||||||
@ -46,36 +50,38 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputBuffer() {
|
public void queueInputBuffer() {
|
||||||
|
if (buffer.data != null && buffer.data.hasRemaining()) {
|
||||||
hasPendingBuffer = true;
|
hasPendingBuffer = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processData() {
|
public void release() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean processDataUpToMuxer() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Format getOutputFormat() {
|
protected Format getMuxerInputFormat() {
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DecoderInputBuffer getOutputBuffer() {
|
protected DecoderInputBuffer getMuxerInputBuffer() {
|
||||||
return hasPendingBuffer ? buffer : null;
|
return hasPendingBuffer ? buffer : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseOutputBuffer() {
|
protected void releaseMuxerInputBuffer() {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
hasPendingBuffer = false;
|
hasPendingBuffer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
protected boolean isMuxerInputEnded() {
|
||||||
return buffer.isEndOfStream();
|
return buffer.isEndOfStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {}
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package com.google.android.exoplayer2.transformer;
|
package com.google.android.exoplayer2.transformer;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,21 +44,6 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
|||||||
*/
|
*/
|
||||||
boolean processData() throws TransformationException;
|
boolean processData() throws TransformationException;
|
||||||
|
|
||||||
/** Returns the output format of the pipeline if available, and {@code null} otherwise. */
|
|
||||||
@Nullable
|
|
||||||
Format getOutputFormat() throws TransformationException;
|
|
||||||
|
|
||||||
/** Returns an output buffer if the pipeline has produced output, and {@code null} otherwise */
|
|
||||||
@Nullable
|
|
||||||
DecoderInputBuffer getOutputBuffer() throws TransformationException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases the pipeline's output buffer.
|
|
||||||
*
|
|
||||||
* <p>Should be called when the output buffer from {@link #getOutputBuffer()} is no longer needed.
|
|
||||||
*/
|
|
||||||
void releaseOutputBuffer() throws TransformationException;
|
|
||||||
|
|
||||||
/** Returns whether the pipeline has ended. */
|
/** Returns whether the pipeline has ended. */
|
||||||
boolean isEnded();
|
boolean isEnded();
|
||||||
|
|
||||||
|
@ -77,16 +77,22 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
|
|||||||
Format inputFormat = checkNotNull(formatHolder.format);
|
Format inputFormat = checkNotNull(formatHolder.format);
|
||||||
if (shouldPassthrough(inputFormat)) {
|
if (shouldPassthrough(inputFormat)) {
|
||||||
samplePipeline =
|
samplePipeline =
|
||||||
new PassthroughSamplePipeline(inputFormat, transformationRequest, fallbackListener);
|
new PassthroughSamplePipeline(
|
||||||
|
inputFormat,
|
||||||
|
streamStartPositionUs,
|
||||||
|
transformationRequest,
|
||||||
|
muxerWrapper,
|
||||||
|
fallbackListener);
|
||||||
} else {
|
} else {
|
||||||
samplePipeline =
|
samplePipeline =
|
||||||
new AudioTranscodingSamplePipeline(
|
new AudioTranscodingSamplePipeline(
|
||||||
inputFormat,
|
inputFormat,
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
|
streamStartPositionUs,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
decoderFactory,
|
decoderFactory,
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
muxerWrapper.getSupportedSampleMimeTypes(getTrackType()),
|
muxerWrapper,
|
||||||
fallbackListener);
|
fallbackListener);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -41,8 +41,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
protected final FallbackListener fallbackListener;
|
protected final FallbackListener fallbackListener;
|
||||||
|
|
||||||
private boolean isTransformationRunning;
|
private boolean isTransformationRunning;
|
||||||
private boolean muxerWrapperTrackAdded;
|
|
||||||
private boolean muxerWrapperTrackEnded;
|
|
||||||
protected long streamOffsetUs;
|
protected long streamOffsetUs;
|
||||||
protected long streamStartPositionUs;
|
protected long streamStartPositionUs;
|
||||||
protected @MonotonicNonNull SamplePipeline samplePipeline;
|
protected @MonotonicNonNull SamplePipeline samplePipeline;
|
||||||
@ -88,7 +86,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isEnded() {
|
public final boolean isEnded() {
|
||||||
return muxerWrapperTrackEnded;
|
return samplePipeline != null && samplePipeline.isEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -98,15 +96,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
|
while (samplePipeline.processData() || feedPipelineFromInput()) {}
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
isTransformationRunning = false;
|
isTransformationRunning = false;
|
||||||
asyncErrorListener.onTransformationException(e);
|
asyncErrorListener.onTransformationException(e);
|
||||||
} catch (Muxer.MuxerException e) {
|
|
||||||
isTransformationRunning = false;
|
|
||||||
asyncErrorListener.onTransformationException(
|
|
||||||
TransformationException.createForMuxer(
|
|
||||||
e, TransformationException.ERROR_CODE_MUXING_FAILED));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,8 +131,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
if (samplePipeline != null) {
|
if (samplePipeline != null) {
|
||||||
samplePipeline.release();
|
samplePipeline.release();
|
||||||
}
|
}
|
||||||
muxerWrapperTrackAdded = false;
|
|
||||||
muxerWrapperTrackEnded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ForOverride
|
@ForOverride
|
||||||
@ -152,49 +143,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
samplePipeline.queueInputBuffer();
|
samplePipeline.queueInputBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to write sample pipeline output data to the muxer.
|
|
||||||
*
|
|
||||||
* @return Whether it may be possible to write more data immediately by calling this method again.
|
|
||||||
* @throws Muxer.MuxerException If a muxing problem occurs.
|
|
||||||
* @throws TransformationException If a {@link SamplePipeline} problem occurs.
|
|
||||||
*/
|
|
||||||
@RequiresNonNull("samplePipeline")
|
|
||||||
private boolean feedMuxerFromPipeline() throws Muxer.MuxerException, TransformationException {
|
|
||||||
if (!muxerWrapperTrackAdded) {
|
|
||||||
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
|
|
||||||
if (samplePipelineOutputFormat == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
muxerWrapperTrackAdded = true;
|
|
||||||
muxerWrapper.addTrackFormat(samplePipelineOutputFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samplePipeline.isEnded()) {
|
|
||||||
muxerWrapper.endTrack(getTrackType());
|
|
||||||
muxerWrapperTrackEnded = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable DecoderInputBuffer samplePipelineOutputBuffer = samplePipeline.getOutputBuffer();
|
|
||||||
if (samplePipelineOutputBuffer == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long samplePresentationTimeUs = samplePipelineOutputBuffer.timeUs - streamStartPositionUs;
|
|
||||||
// TODO(b/204892224): Consider subtracting the first sample timestamp from the sample pipeline
|
|
||||||
// buffer from all samples so that they are guaranteed to start from zero in the output file.
|
|
||||||
if (!muxerWrapper.writeSample(
|
|
||||||
getTrackType(),
|
|
||||||
checkStateNotNull(samplePipelineOutputBuffer.data),
|
|
||||||
samplePipelineOutputBuffer.isKeyFrame(),
|
|
||||||
samplePresentationTimeUs)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
samplePipeline.releaseOutputBuffer();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to read input data and pass the input data to the sample pipeline.
|
* Attempts to read input data and pass the input data to the sample pipeline.
|
||||||
*
|
*
|
||||||
|
@ -103,18 +103,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
context,
|
context,
|
||||||
inputFormat,
|
inputFormat,
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
|
streamStartPositionUs,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
effects,
|
effects,
|
||||||
frameProcessorFactory,
|
frameProcessorFactory,
|
||||||
decoderFactory,
|
decoderFactory,
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
muxerWrapper.getSupportedSampleMimeTypes(getTrackType()),
|
muxerWrapper,
|
||||||
fallbackListener,
|
fallbackListener,
|
||||||
asyncErrorListener,
|
asyncErrorListener,
|
||||||
debugViewProvider);
|
debugViewProvider);
|
||||||
} else {
|
} else {
|
||||||
samplePipeline =
|
samplePipeline =
|
||||||
new PassthroughSamplePipeline(inputFormat, transformationRequest, fallbackListener);
|
new PassthroughSamplePipeline(
|
||||||
|
inputFormat,
|
||||||
|
streamStartPositionUs,
|
||||||
|
transformationRequest,
|
||||||
|
muxerWrapper,
|
||||||
|
fallbackListener);
|
||||||
}
|
}
|
||||||
if (transformationRequest.flattenForSlowMotion) {
|
if (transformationRequest.flattenForSlowMotion) {
|
||||||
sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat);
|
sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat);
|
||||||
|
@ -50,7 +50,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
/**
|
/**
|
||||||
* Pipeline to decode video samples, apply transformations on the raw samples, and re-encode them.
|
* Pipeline to decode video samples, apply transformations on the raw samples, and re-encode them.
|
||||||
*/
|
*/
|
||||||
/* package */ final class VideoTranscodingSamplePipeline implements SamplePipeline {
|
/* package */ final class VideoTranscodingSamplePipeline extends BaseSamplePipeline {
|
||||||
|
|
||||||
private final int maxPendingFrameCount;
|
private final int maxPendingFrameCount;
|
||||||
|
|
||||||
@ -67,16 +67,19 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
Context context,
|
Context context,
|
||||||
Format inputFormat,
|
Format inputFormat,
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
|
long streamStartPositionUs,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
ImmutableList<Effect> effects,
|
ImmutableList<Effect> effects,
|
||||||
FrameProcessor.Factory frameProcessorFactory,
|
FrameProcessor.Factory frameProcessorFactory,
|
||||||
Codec.DecoderFactory decoderFactory,
|
Codec.DecoderFactory decoderFactory,
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
List<String> allowedOutputMimeTypes,
|
MuxerWrapper muxerWrapper,
|
||||||
FallbackListener fallbackListener,
|
FallbackListener fallbackListener,
|
||||||
Transformer.AsyncErrorListener asyncErrorListener,
|
Transformer.AsyncErrorListener asyncErrorListener,
|
||||||
DebugViewProvider debugViewProvider)
|
DebugViewProvider debugViewProvider)
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
|
super(C.TRACK_TYPE_VIDEO, streamStartPositionUs, muxerWrapper);
|
||||||
|
|
||||||
if (ColorInfo.isTransferHdr(inputFormat.colorInfo)
|
if (ColorInfo.isTransferHdr(inputFormat.colorInfo)
|
||||||
&& (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround())) {
|
&& (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround())) {
|
||||||
throw TransformationException.createForCodec(
|
throw TransformationException.createForCodec(
|
||||||
@ -119,7 +122,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
new EncoderWrapper(
|
new EncoderWrapper(
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
inputFormat,
|
inputFormat,
|
||||||
allowedOutputMimeTypes,
|
muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO),
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
fallbackListener);
|
fallbackListener);
|
||||||
|
|
||||||
@ -199,7 +202,14 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processData() throws TransformationException {
|
public void release() {
|
||||||
|
frameProcessor.release();
|
||||||
|
decoder.release();
|
||||||
|
encoderWrapper.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean processDataUpToMuxer() throws TransformationException {
|
||||||
if (decoder.isEnded()) {
|
if (decoder.isEnded()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -217,13 +227,13 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Format getOutputFormat() throws TransformationException {
|
protected Format getMuxerInputFormat() throws TransformationException {
|
||||||
return encoderWrapper.getOutputFormat();
|
return encoderWrapper.getOutputFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DecoderInputBuffer getOutputBuffer() throws TransformationException {
|
protected DecoderInputBuffer getMuxerInputBuffer() throws TransformationException {
|
||||||
encoderOutputBuffer.data = encoderWrapper.getOutputBuffer();
|
encoderOutputBuffer.data = encoderWrapper.getOutputBuffer();
|
||||||
if (encoderOutputBuffer.data == null) {
|
if (encoderOutputBuffer.data == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -235,22 +245,15 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseOutputBuffer() throws TransformationException {
|
protected void releaseMuxerInputBuffer() throws TransformationException {
|
||||||
encoderWrapper.releaseOutputBuffer(/* render= */ false);
|
encoderWrapper.releaseOutputBuffer(/* render= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
protected boolean isMuxerInputEnded() {
|
||||||
return encoderWrapper.isEnded();
|
return encoderWrapper.isEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
frameProcessor.release();
|
|
||||||
decoder.release();
|
|
||||||
encoderWrapper.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link TransformationRequest}, based on an original {@code TransformationRequest} and
|
* Creates a {@link TransformationRequest}, based on an original {@code TransformationRequest} and
|
||||||
* parameters specifying alterations to it that indicate device support.
|
* parameters specifying alterations to it that indicate device support.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user