mirror of
https://github.com/androidx/media.git
synced 2025-05-14 11:09:53 +08:00
Rename SamplePipeline to SampleExporter
PiperOrigin-RevId: 545974776
This commit is contained in:
parent
99a143a74e
commit
50c6efe95d
@ -186,7 +186,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
/**
|
||||
* Attempts to feed input data to the {@link AudioProcessingPipeline}.
|
||||
*
|
||||
* @return Whether the {@link AudioSamplePipeline} may be able to continue processing data.
|
||||
* @return Whether it may be possible to process more data immediately by calling this method
|
||||
* again.
|
||||
*/
|
||||
private boolean feedProcessingPipelineFromInput() {
|
||||
if (shouldGenerateSilence()) {
|
||||
|
@ -32,8 +32,8 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
/** Pipeline to process, re-encode and mux raw audio samples. */
|
||||
/* package */ final class AudioSamplePipeline extends SamplePipeline {
|
||||
/** Processes, re-encodes and muxes raw audio samples. */
|
||||
/* package */ final class AudioSampleExporter extends SampleExporter {
|
||||
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
||||
|
||||
private final Codec encoder;
|
||||
@ -44,9 +44,9 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
private long encoderTotalInputBytes;
|
||||
|
||||
public AudioSamplePipeline(
|
||||
public AudioSampleExporter(
|
||||
Format firstAssetLoaderInputFormat,
|
||||
Format firstPipelineInputFormat,
|
||||
Format firstExporterInputFormat,
|
||||
TransformationRequest transformationRequest,
|
||||
EditedMediaItem firstEditedMediaItem,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
@ -54,10 +54,10 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
FallbackListener fallbackListener)
|
||||
throws ExportException {
|
||||
super(firstAssetLoaderInputFormat, muxerWrapper);
|
||||
checkArgument(firstPipelineInputFormat.pcmEncoding != Format.NO_VALUE);
|
||||
checkArgument(firstExporterInputFormat.pcmEncoding != Format.NO_VALUE);
|
||||
|
||||
try {
|
||||
audioGraph = new AudioGraph(firstPipelineInputFormat, firstEditedMediaItem);
|
||||
audioGraph = new AudioGraph(firstExporterInputFormat, firstEditedMediaItem);
|
||||
} catch (AudioProcessor.UnhandledAudioFormatException e) {
|
||||
throw ExportException.createForAudioProcessing(e, e.inputAudioFormat);
|
||||
}
|
@ -27,8 +27,8 @@ import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/** Pipeline that muxes encoded samples without any transcoding or transformation. */
|
||||
/* package */ final class EncodedSamplePipeline extends SamplePipeline {
|
||||
/** Muxes encoded samples without any transcoding or transformation. */
|
||||
/* package */ final class EncodedSampleExporter extends SampleExporter {
|
||||
|
||||
private static final int MAX_INPUT_BUFFER_COUNT = 10;
|
||||
|
||||
@ -41,7 +41,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
private volatile boolean inputEnded;
|
||||
|
||||
public EncodedSamplePipeline(
|
||||
public EncodedSampleExporter(
|
||||
Format format,
|
||||
TransformationRequest transformationRequest,
|
||||
MuxerWrapper muxerWrapper,
|
@ -164,7 +164,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.build());
|
||||
// Arbitrarily decrease buffers for playback so that samples start being sent earlier to the
|
||||
// pipelines (rebuffers are less problematic for the export use case).
|
||||
// exporters (rebuffers are less problematic for the export use case).
|
||||
DefaultLoadControl loadControl =
|
||||
new DefaultLoadControl.Builder()
|
||||
.setBufferDurationsMs(
|
||||
|
@ -19,20 +19,20 @@ import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
|
||||
/** A listener for {@link MediaItem} changes in the {@linkplain SamplePipeline sample pipelines}. */
|
||||
/** A listener for {@link MediaItem} changes in the {@linkplain SampleExporter sample exporters}. */
|
||||
/* package */ interface OnMediaItemChangedListener {
|
||||
|
||||
/**
|
||||
* Called when the {@link MediaItem} whose samples are passed to the {@link SamplePipeline}
|
||||
* Called when the {@link MediaItem} whose samples are passed to the {@link SampleExporter}
|
||||
* changes.
|
||||
*
|
||||
* @param editedMediaItem The {@link MediaItem} with the transformations to apply to it.
|
||||
* @param durationUs The duration of the {@link MediaItem}, in microseconds.
|
||||
* @param trackFormat The {@link Format} extracted (and possibly decoded) from the {@link
|
||||
* MediaItem} track, which represents the samples input to the {@link SamplePipeline}. {@code
|
||||
* MediaItem} track, which represents the samples input to the {@link SampleExporter}. {@code
|
||||
* null} if no such track was extracted.
|
||||
* @param isLast Whether the {@link MediaItem} is the last one passed to the {@link
|
||||
* SamplePipeline}.
|
||||
* SampleExporter}.
|
||||
*/
|
||||
void onMediaItemChanged(
|
||||
EditedMediaItem editedMediaItem,
|
||||
|
@ -35,17 +35,17 @@ import com.google.common.collect.ImmutableSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pipeline for processing media data.
|
||||
* Exporter that processes media data.
|
||||
*
|
||||
* <p>This pipeline can be used to implement transformations of audio or video samples.
|
||||
* <p>This exporter can be used to implement transformations of audio or video samples.
|
||||
*
|
||||
* <p>The {@link SampleConsumer} and {@link OnMediaItemChangedListener} methods must be called from
|
||||
* the same thread. This thread can change when the {@link
|
||||
* OnMediaItemChangedListener#onMediaItemChanged(EditedMediaItem, long, Format, boolean) MediaItem}
|
||||
* changes, and can be different from the thread used to call the other {@code SamplePipeline}
|
||||
* changes, and can be different from the thread used to call the other {@code SampleExporter}
|
||||
* methods.
|
||||
*/
|
||||
/* package */ abstract class SamplePipeline implements SampleConsumer, OnMediaItemChangedListener {
|
||||
/* package */ abstract class SampleExporter implements SampleConsumer, OnMediaItemChangedListener {
|
||||
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final @C.TrackType int outputTrackType;
|
||||
@ -53,7 +53,7 @@ import java.util.List;
|
||||
|
||||
private boolean muxerWrapperTrackAdded;
|
||||
|
||||
public SamplePipeline(Format firstInputFormat, MuxerWrapper muxerWrapper) {
|
||||
public SampleExporter(Format firstInputFormat, MuxerWrapper muxerWrapper) {
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
this.metadata = firstInputFormat.metadata;
|
||||
outputTrackType = getProcessedTrackType(firstInputFormat.sampleMimeType);
|
||||
@ -67,7 +67,7 @@ import java.util.List;
|
||||
return feedMuxer() || (!isMuxerInputEnded() && processDataUpToMuxer());
|
||||
}
|
||||
|
||||
/** Releases all resources held by the pipeline. */
|
||||
/** Releases all resources held by the exporter. */
|
||||
public abstract void release();
|
||||
|
||||
protected boolean processDataUpToMuxer() throws ExportException {
|
@ -93,13 +93,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
// Internal messages.
|
||||
private static final int MSG_START = 0;
|
||||
private static final int MSG_REGISTER_SAMPLE_PIPELINE = 1;
|
||||
private static final int MSG_DRAIN_PIPELINES = 2;
|
||||
private static final int MSG_REGISTER_SAMPLE_EXPORTER = 1;
|
||||
private static final int MSG_DRAIN_EXPORTERS = 2;
|
||||
private static final int MSG_END = 3;
|
||||
private static final int MSG_UPDATE_PROGRESS = 4;
|
||||
|
||||
private static final String TAG = "TransformerInternal";
|
||||
private static final int DRAIN_PIPELINES_DELAY_MS = 10;
|
||||
private static final int DRAIN_EXPORTERS_DELAY_MS = 10;
|
||||
|
||||
private final Context context;
|
||||
private final Composition composition;
|
||||
@ -115,13 +115,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final AtomicInteger tracksToAdd;
|
||||
private final AtomicBoolean outputHasAudio;
|
||||
private final AtomicBoolean outputHasVideo;
|
||||
private final List<SamplePipeline> samplePipelines;
|
||||
private final List<SampleExporter> sampleExporters;
|
||||
private final Object setMaxSequenceDurationUsLock;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final ConditionVariable transformerConditionVariable;
|
||||
private final ExportResult.Builder exportResultBuilder;
|
||||
|
||||
private boolean isDrainingPipelines;
|
||||
private boolean isDrainingExporters;
|
||||
private long currentMaxSequenceDurationUs;
|
||||
private int nonLoopingSequencesWithNonFinalDuration;
|
||||
private @Transformer.ProgressState int progressState;
|
||||
@ -185,7 +185,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
tracksToAdd = new AtomicInteger();
|
||||
outputHasAudio = new AtomicBoolean();
|
||||
outputHasVideo = new AtomicBoolean();
|
||||
samplePipelines = new ArrayList<>();
|
||||
sampleExporters = new ArrayList<>();
|
||||
setMaxSequenceDurationUsLock = new Object();
|
||||
transformerConditionVariable = new ConditionVariable();
|
||||
exportResultBuilder = new ExportResult.Builder();
|
||||
@ -288,11 +288,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
case MSG_START:
|
||||
startInternal();
|
||||
break;
|
||||
case MSG_REGISTER_SAMPLE_PIPELINE:
|
||||
registerSamplePipelineInternal((SamplePipeline) msg.obj);
|
||||
case MSG_REGISTER_SAMPLE_EXPORTER:
|
||||
registerSampleExporterInternal((SampleExporter) msg.obj);
|
||||
break;
|
||||
case MSG_DRAIN_PIPELINES:
|
||||
drainPipelinesInternal();
|
||||
case MSG_DRAIN_EXPORTERS:
|
||||
drainExportersInternal();
|
||||
break;
|
||||
case MSG_END:
|
||||
endInternal(/* endReason= */ msg.arg1, /* exportException= */ (ExportException) msg.obj);
|
||||
@ -317,21 +317,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerSamplePipelineInternal(SamplePipeline samplePipeline) {
|
||||
samplePipelines.add(samplePipeline);
|
||||
if (!isDrainingPipelines) {
|
||||
internalHandler.sendEmptyMessage(MSG_DRAIN_PIPELINES);
|
||||
isDrainingPipelines = true;
|
||||
private void registerSampleExporterInternal(SampleExporter sampleExporter) {
|
||||
sampleExporters.add(sampleExporter);
|
||||
if (!isDrainingExporters) {
|
||||
internalHandler.sendEmptyMessage(MSG_DRAIN_EXPORTERS);
|
||||
isDrainingExporters = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void drainPipelinesInternal() throws ExportException {
|
||||
for (int i = 0; i < samplePipelines.size(); i++) {
|
||||
while (samplePipelines.get(i).processData()) {}
|
||||
private void drainExportersInternal() throws ExportException {
|
||||
for (int i = 0; i < sampleExporters.size(); i++) {
|
||||
while (sampleExporters.get(i).processData()) {}
|
||||
}
|
||||
|
||||
if (!muxerWrapper.isEnded()) {
|
||||
internalHandler.sendEmptyMessageDelayed(MSG_DRAIN_PIPELINES, DRAIN_PIPELINES_DELAY_MS);
|
||||
internalHandler.sendEmptyMessageDelayed(MSG_DRAIN_EXPORTERS, DRAIN_EXPORTERS_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,12 +351,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
boolean releasedPreviously = released;
|
||||
if (!released) {
|
||||
released = true;
|
||||
// The video sample pipeline can hold buffers from the asset loader's decoder in a surface
|
||||
// texture, so we release the video sample pipeline first to avoid releasing the codec while
|
||||
// its buffers are pending processing.
|
||||
for (int i = 0; i < samplePipelines.size(); i++) {
|
||||
// VideoSampleExporter can hold buffers from the asset loader's decoder in a surface texture,
|
||||
// so we release the VideoSampleExporter first to avoid releasing the codec while its buffers
|
||||
// are pending processing.
|
||||
for (int i = 0; i < sampleExporters.size(); i++) {
|
||||
try {
|
||||
samplePipelines.get(i).release();
|
||||
sampleExporters.get(i).release();
|
||||
} catch (RuntimeException e) {
|
||||
if (releaseExportException == null) {
|
||||
releaseExportException = ExportException.createForUnexpected(e);
|
||||
@ -525,19 +525,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@C.TrackType int trackType = getProcessedTrackType(assetLoaderOutputFormat.sampleMimeType);
|
||||
AddedTrackInfo trackInfo = checkStateNotNull(addedTrackInfoByTrackType.get(trackType));
|
||||
SamplePipeline samplePipeline = getSamplePipeline(assetLoaderOutputFormat, trackInfo);
|
||||
SampleExporter sampleExporter = getSampleExporter(assetLoaderOutputFormat, trackInfo);
|
||||
|
||||
OnMediaItemChangedListener onMediaItemChangedListener =
|
||||
(editedMediaItem, durationUs, trackFormat, isLast) -> {
|
||||
onMediaItemChanged(trackType, durationUs, isLast);
|
||||
samplePipeline.onMediaItemChanged(editedMediaItem, durationUs, trackFormat, isLast);
|
||||
sampleExporter.onMediaItemChanged(editedMediaItem, durationUs, trackFormat, isLast);
|
||||
};
|
||||
sequenceAssetLoaders
|
||||
.get(sequenceIndex)
|
||||
.addOnMediaItemChangedListener(onMediaItemChangedListener, trackType);
|
||||
|
||||
internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget();
|
||||
return samplePipeline;
|
||||
internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_EXPORTER, sampleExporter).sendToTarget();
|
||||
return sampleExporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -547,14 +547,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
// Private methods.
|
||||
|
||||
private SamplePipeline getSamplePipeline(
|
||||
private SampleExporter getSampleExporter(
|
||||
Format firstAssetLoaderOutputFormat, AddedTrackInfo addedTrackInfo) throws ExportException {
|
||||
if (addedTrackInfo.shouldTranscode) {
|
||||
EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0);
|
||||
if (MimeTypes.isAudio(firstAssetLoaderOutputFormat.sampleMimeType)) {
|
||||
return new AudioSamplePipeline(
|
||||
return new AudioSampleExporter(
|
||||
addedTrackInfo.firstAssetLoaderInputFormat,
|
||||
/* firstPipelineInputFormat= */ firstAssetLoaderOutputFormat,
|
||||
/* firstExporterInputFormat= */ firstAssetLoaderOutputFormat,
|
||||
transformationRequest,
|
||||
firstEditedMediaItem,
|
||||
encoderFactory,
|
||||
@ -569,7 +569,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
: (Presentation) compositionVideoEffects.get(0);
|
||||
|
||||
// TODO(b/267301878): Pass firstAssetLoaderOutputFormat once surface creation not in VSP.
|
||||
return new VideoSamplePipeline(
|
||||
return new VideoSampleExporter(
|
||||
context,
|
||||
addedTrackInfo.firstAssetLoaderInputFormat,
|
||||
transformationRequest,
|
||||
@ -583,7 +583,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
return new EncodedSamplePipeline(
|
||||
return new EncodedSampleExporter(
|
||||
firstAssetLoaderOutputFormat, transformationRequest, muxerWrapper, fallbackListener);
|
||||
}
|
||||
|
||||
|
@ -64,10 +64,10 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
/** Pipeline to process, re-encode and mux raw video frames. */
|
||||
/* package */ final class VideoSamplePipeline extends SamplePipeline {
|
||||
/** Processes, re-encodes and muxes raw video frames. */
|
||||
/* package */ final class VideoSampleExporter extends SampleExporter {
|
||||
|
||||
private static final String TAG = "VideoSamplePipeline";
|
||||
private static final String TAG = "VideoSampleExporter";
|
||||
private final AtomicLong mediaItemOffsetUs;
|
||||
private final VideoFrameProcessor videoFrameProcessor;
|
||||
private final ColorInfo videoFrameProcessorInputColor;
|
||||
@ -83,7 +83,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
*/
|
||||
private volatile long finalFramePresentationTimeUs;
|
||||
|
||||
public VideoSamplePipeline(
|
||||
public VideoSampleExporter(
|
||||
Context context,
|
||||
Format firstInputFormat,
|
||||
TransformationRequest transformationRequest,
|
||||
@ -96,8 +96,8 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
DebugViewProvider debugViewProvider)
|
||||
throws ExportException {
|
||||
// TODO(b/262693177) Add tests for input format change.
|
||||
// TODO(b/278259383) Consider delaying configuration of VideoSamplePipeline to use the decoder
|
||||
// output format instead of the extractor output format, to match AudioSamplePipeline behavior.
|
||||
// TODO(b/278259383) Consider delaying configuration of VideoSampleExporter to use the decoder
|
||||
// output format instead of the extractor output format, to match AudioSampleExporter behavior.
|
||||
super(firstInputFormat, muxerWrapper);
|
||||
|
||||
mediaItemOffsetUs = new AtomicLong();
|
||||
@ -189,7 +189,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
@Override
|
||||
public void onEnded() {
|
||||
VideoSamplePipeline.this.finalFramePresentationTimeUs =
|
||||
VideoSampleExporter.this.finalFramePresentationTimeUs =
|
||||
lastProcessedFramePresentationTimeUs;
|
||||
try {
|
||||
encoderWrapper.signalEndOfInputStream();
|
@ -905,7 +905,7 @@ public final class MediaItemExportTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_withAssetLoaderAlwaysDecoding_pipelineExpectsDecoded() throws Exception {
|
||||
public void start_withAssetLoaderAlwaysDecoding_exporterExpectsDecoded() throws Exception {
|
||||
AtomicReference<SampleConsumer> sampleConsumerRef = new AtomicReference<>();
|
||||
Transformer transformer =
|
||||
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
|
||||
@ -917,7 +917,7 @@ public final class MediaItemExportTest {
|
||||
transformer.start(mediaItem, outputPath);
|
||||
runLooperUntil(transformer.getApplicationLooper(), () -> sampleConsumerRef.get() != null);
|
||||
|
||||
assertThat(sampleConsumerRef.get()).isNotInstanceOf(EncodedSamplePipeline.class);
|
||||
assertThat(sampleConsumerRef.get()).isNotInstanceOf(EncodedSampleExporter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,7 +41,7 @@ import org.robolectric.shadows.MediaCodecInfoBuilder;
|
||||
import org.robolectric.shadows.ShadowMediaCodec;
|
||||
import org.robolectric.shadows.ShadowMediaCodecList;
|
||||
|
||||
/** Unit tests for {@link VideoSamplePipeline.EncoderWrapper}. */
|
||||
/** Unit tests for {@link VideoSampleExporter.EncoderWrapper}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class VideoEncoderWrapperTest {
|
||||
private static final Composition FAKE_COMPOSITION =
|
||||
@ -61,8 +61,8 @@ public final class VideoEncoderWrapperTest {
|
||||
new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, (listener, flags) -> {}),
|
||||
Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null),
|
||||
emptyTransformationRequest);
|
||||
private final VideoSamplePipeline.EncoderWrapper encoderWrapper =
|
||||
new VideoSamplePipeline.EncoderWrapper(
|
||||
private final VideoSampleExporter.EncoderWrapper encoderWrapper =
|
||||
new VideoSampleExporter.EncoderWrapper(
|
||||
fakeEncoderFactory,
|
||||
/* inputFormat= */ new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
|
Loading…
x
Reference in New Issue
Block a user