listeners;
@Nullable private AssetLoader.Factory assetLoaderFactory;
private FrameProcessor.Factory frameProcessorFactory;
@@ -125,7 +125,7 @@ public final class Transformer {
this.videoEffects = transformer.videoEffects;
this.removeAudio = transformer.removeAudio;
this.removeVideo = transformer.removeVideo;
- this.forceSilentAudio = transformer.forceSilentAudio;
+ this.generateSilentAudio = transformer.generateSilentAudio;
this.listeners = transformer.listeners;
this.assetLoaderFactory = transformer.assetLoaderFactory;
this.frameProcessorFactory = transformer.frameProcessorFactory;
@@ -400,29 +400,32 @@ public final class Transformer {
}
/**
- * Sets whether to force silent audio for the output file, ignoring any existing audio.
+ * Sets whether to generate silent audio for the output file, if there is no audio available.
*
* This method is experimental and may be removed or changed without warning.
*
+ *
To replace existing audio with silence, call {@link #setRemoveAudio(boolean)} as well.
+ *
*
Audio properties/format:
*
*
* - Duration will match duration of the input media.
*
- Sample mime type will match {@link TransformationRequest#audioMimeType}, or {@link
* MimeTypes#AUDIO_AAC} if {@code null}.
- *
- Sample rate will be 44100hz. This can be modified by passing a {@link
+ *
- Sample rate will be {@code 44100} hz. This can be modified by passing a {@link
* SonicAudioProcessor} to {@link #setAudioProcessors(List)}, using {@link
* SonicAudioProcessor#setOutputSampleRateHz(int)}.
- *
- Channel count will be 2. This can be modified by implementing a custom {@link
+ *
- Channel count will be {@code 2}. This can be modified by implementing a custom {@link
* AudioProcessor} and passing it to {@link #setAudioProcessors(List)}.
*
*
- * @param forceSilentAudio Whether to output silent audio for the output file.
+ * @param generateSilentAudio Whether to generate silent audio for the output file if there is
+ * no audio track.
* @return This builder.
*/
@CanIgnoreReturnValue
- public Builder experimentalSetForceSilentAudio(boolean forceSilentAudio) {
- this.forceSilentAudio = forceSilentAudio;
+ public Builder experimentalSetGenerateSilentAudio(boolean generateSilentAudio) {
+ this.generateSilentAudio = generateSilentAudio;
return this;
}
@@ -459,7 +462,7 @@ public final class Transformer {
videoEffects,
removeAudio,
removeVideo,
- forceSilentAudio,
+ generateSilentAudio,
listeners,
assetLoaderFactory,
frameProcessorFactory,
@@ -579,7 +582,7 @@ public final class Transformer {
private final ImmutableList videoEffects;
private final boolean removeAudio;
private final boolean removeVideo;
- private final boolean forceSilentAudio;
+ private final boolean generateSilentAudio;
private final ListenerSet listeners;
private final AssetLoader.Factory assetLoaderFactory;
private final FrameProcessor.Factory frameProcessorFactory;
@@ -598,7 +601,7 @@ public final class Transformer {
ImmutableList videoEffects,
boolean removeAudio,
boolean removeVideo,
- boolean forceSilentAudio,
+ boolean generateSilentAudio,
ListenerSet listeners,
AssetLoader.Factory assetLoaderFactory,
FrameProcessor.Factory frameProcessorFactory,
@@ -607,10 +610,6 @@ public final class Transformer {
Looper looper,
DebugViewProvider debugViewProvider,
Clock clock) {
- if (forceSilentAudio) {
- removeAudio = true;
- }
- checkState(!removeVideo || !forceSilentAudio, "Silent only audio track needs a video track.");
checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed.");
this.context = context;
this.transformationRequest = transformationRequest;
@@ -618,7 +617,7 @@ public final class Transformer {
this.videoEffects = videoEffects;
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
- this.forceSilentAudio = forceSilentAudio;
+ this.generateSilentAudio = generateSilentAudio;
this.listeners = listeners;
this.assetLoaderFactory = assetLoaderFactory;
this.frameProcessorFactory = frameProcessorFactory;
@@ -761,7 +760,7 @@ public final class Transformer {
videoEffects,
removeAudio,
removeVideo,
- forceSilentAudio,
+ generateSilentAudio,
assetLoaderFactory,
frameProcessorFactory,
encoderFactory,
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
index 3f1f4c4652..caae2c8a01 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
@@ -93,7 +93,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final TransformationRequest transformationRequest;
private final ImmutableList audioProcessors;
private final ImmutableList videoEffects;
- private final boolean forceSilentAudio;
private final FrameProcessor.Factory frameProcessorFactory;
private final CapturingEncoderFactory encoderFactory;
private final Listener listener;
@@ -108,6 +107,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final ConditionVariable transformerConditionVariable;
private final TransformationResult.Builder transformationResultBuilder;
+ private boolean generateSilentAudio;
private boolean isDrainingPipelines;
private @Transformer.ProgressState int progressState;
private @MonotonicNonNull RuntimeException cancelException;
@@ -124,7 +124,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ImmutableList videoEffects,
boolean removeAudio,
boolean removeVideo,
- boolean forceSilentAudio,
+ boolean generateSilentAudio,
AssetLoader.Factory assetLoaderFactory,
FrameProcessor.Factory frameProcessorFactory,
Codec.EncoderFactory encoderFactory,
@@ -138,7 +138,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.transformationRequest = transformationRequest;
this.audioProcessors = audioProcessors;
this.videoEffects = videoEffects;
- this.forceSilentAudio = forceSilentAudio;
+ this.generateSilentAudio = generateSilentAudio;
this.frameProcessorFactory = frameProcessorFactory;
this.encoderFactory = new CapturingEncoderFactory(encoderFactory);
this.listener = listener;
@@ -373,9 +373,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return;
}
this.trackCount.set(trackCount);
- if (forceSilentAudio) {
- this.trackCount.incrementAndGet();
- }
}
@Override
@@ -386,6 +383,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long streamOffsetUs)
throws TransformationException {
if (!trackAdded) {
+ if (generateSilentAudio) {
+ if (this.trackCount.get() == 1 && MimeTypes.isVideo(format.sampleMimeType)) {
+ this.trackCount.incrementAndGet();
+ } else {
+ generateSilentAudio = false;
+ }
+ }
+
// Call setTrackCount() methods here so that they are called from the same thread as the
// MuxerWrapper and FallbackListener methods called when building the sample pipelines.
muxerWrapper.setTrackCount(trackCount.get());
@@ -397,7 +402,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
getSamplePipeline(format, supportedOutputTypes, streamStartPositionUs, streamOffsetUs);
internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget();
- if (forceSilentAudio) {
+ if (generateSilentAudio) {
Format silentAudioFormat =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_AAC)
@@ -482,7 +487,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
streamOffsetUs,
transformationRequest,
audioProcessors,
- forceSilentAudio ? durationUs : C.TIME_UNSET,
+ generateSilentAudio ? durationUs : C.TIME_UNSET,
encoderFactory,
muxerWrapper,
fallbackListener);
@@ -528,7 +533,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (!audioProcessors.isEmpty()) {
return true;
}
- if (forceSilentAudio) {
+ if (generateSilentAudio) {
return true;
}
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
index b544875d71..6324ee38f9 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
@@ -271,10 +271,40 @@ public final class TransformerEndToEndTest {
}
@Test
- public void startTransformation_silentAudio_completesSuccessfully() throws Exception {
+ public void startTransformation_silentAudioOnAudioOnly_isIgnored() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
- .experimentalSetForceSilentAudio(true)
+ .experimentalSetGenerateSilentAudio(true)
+ .build();
+ MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
+
+ transformer.startTransformation(mediaItem, outputPath);
+ TransformerTestRunner.runLooper(transformer);
+
+ DumpFileAsserts.assertOutput(
+ context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
+ }
+
+ @Test
+ public void startTransformation_silentAudioOnAudioVideo_isIgnored() throws Exception {
+ Transformer transformer =
+ createTransformerBuilder(/* enableFallback= */ false)
+ .experimentalSetGenerateSilentAudio(true)
+ .build();
+ MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
+
+ transformer.startTransformation(mediaItem, outputPath);
+ TransformerTestRunner.runLooper(transformer);
+
+ DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
+ }
+
+ @Test
+ public void startTransformation_silentAudioRemoveAudio_completesSuccessfully() throws Exception {
+ Transformer transformer =
+ createTransformerBuilder(/* enableFallback= */ false)
+ .experimentalSetGenerateSilentAudio(true)
+ .setRemoveAudio(true)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
@@ -285,6 +315,36 @@ public final class TransformerEndToEndTest {
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
}
+ @Test
+ public void startTransformation_silentAudioRemoveVideo_isIgnored() throws Exception {
+ Transformer transformer =
+ createTransformerBuilder(/* enableFallback= */ false)
+ .experimentalSetGenerateSilentAudio(true)
+ .setRemoveVideo(true)
+ .build();
+ MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
+
+ transformer.startTransformation(mediaItem, outputPath);
+ TransformerTestRunner.runLooper(transformer);
+ DumpFileAsserts.assertOutput(
+ context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
+ }
+
+ @Test
+ public void startTransformation_silentAudioOnVideoOnly_completesSuccessfully() throws Exception {
+ Transformer transformer =
+ createTransformerBuilder(/* enableFallback= */ false)
+ .experimentalSetGenerateSilentAudio(true)
+ .build();
+ MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
+
+ transformer.startTransformation(mediaItem, outputPath);
+ TransformerTestRunner.runLooper(transformer);
+
+ DumpFileAsserts.assertOutput(
+ context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY + ".silentaudio"));
+ }
+
@Test
public void startTransformation_adjustSampleRate_completesSuccessfully() throws Exception {
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();