Use encoder input format for sample rate fallback.

This ensures that when the input sample rate handled by the encoder differs from the output of the AudioGraph, that output audio will not be broken.

PiperOrigin-RevId: 725610384
This commit is contained in:
Googler 2025-02-11 06:42:25 -08:00 committed by Copybara-Service
parent 1b882fec0c
commit 447d784636
3 changed files with 6 additions and 53 deletions

View File

@ -1370,42 +1370,6 @@ public final class AndroidTestUtil {
throw new AssumptionViolatedException("Profile not supported"); throw new AssumptionViolatedException("Profile not supported");
} }
/**
* Assumes that the given sample rate is unsupported and returns the fallback sample rate the
* device will use to encode.
*
* @param mimeType The {@linkplain MimeTypes MIME type}.
* @param unsupportedSampleRate An unsupported sample rate.
* @return The fallback sample rate.
* @throws AssumptionViolatedException If the device does not have the required encoder or sample
* rate configuration.
*/
public static int getFallbackAssumingUnsupportedSampleRate(
String mimeType, int unsupportedSampleRate) {
ImmutableList<MediaCodecInfo> supportedEncoders = EncoderUtil.getSupportedEncoders(mimeType);
if (supportedEncoders.isEmpty()) {
throw new AssumptionViolatedException("No supported encoders for mime type: " + mimeType);
}
int closestSupportedSampleRate = -1;
int minSampleRateCost = Integer.MAX_VALUE;
for (int i = 0; i < supportedEncoders.size(); i++) {
int actualFallbackSampleRate =
EncoderUtil.getClosestSupportedSampleRate(
supportedEncoders.get(i), mimeType, unsupportedSampleRate);
int sampleRateCost = Math.abs(actualFallbackSampleRate - unsupportedSampleRate);
if (sampleRateCost < minSampleRateCost) {
minSampleRateCost = sampleRateCost;
closestSupportedSampleRate = actualFallbackSampleRate;
}
}
if (closestSupportedSampleRate == unsupportedSampleRate) {
throw new AssumptionViolatedException(
String.format("Expected sample rate %s to be unsupported", unsupportedSampleRate));
}
return closestSupportedSampleRate;
}
/** Returns a {@link Muxer.Factory} depending upon the API level. */ /** Returns a {@link Muxer.Factory} depending upon the API level. */
public static Muxer.Factory getMuxerFactoryBasedOnApi() { public static Muxer.Factory getMuxerFactoryBasedOnApi() {
// MediaMuxer supports B-frame from API > 24. // MediaMuxer supports B-frame from API > 24.

View File

@ -45,7 +45,6 @@ import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported
import static androidx.media3.transformer.AndroidTestUtil.createFrameCountingEffect; import static androidx.media3.transformer.AndroidTestUtil.createFrameCountingEffect;
import static androidx.media3.transformer.AndroidTestUtil.createOpenGlObjects; import static androidx.media3.transformer.AndroidTestUtil.createOpenGlObjects;
import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap; import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap;
import static androidx.media3.transformer.AndroidTestUtil.getFallbackAssumingUnsupportedSampleRate;
import static androidx.media3.transformer.AndroidTestUtil.getMuxerFactoryBasedOnApi; import static androidx.media3.transformer.AndroidTestUtil.getMuxerFactoryBasedOnApi;
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped; import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA; import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA;
@ -119,7 +118,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestName; import org.junit.rules.TestName;
@ -2430,12 +2428,8 @@ public class TransformerEndToEndTest {
} }
@Test @Test
@Ignore("TODO: b/389068218 - Fix this test and re-enable it") public void export_withHighSampleRateAndFallbackEnabled_exportsWithCorrectDuration()
public void export_withUnsupportedSampleRateAndFallbackEnabled_exportsWithFallbackSampleRate()
throws Exception { throws Exception {
int unsupportedSampleRate = 96_000;
int fallbackSampleRate =
getFallbackAssumingUnsupportedSampleRate(MimeTypes.AUDIO_AAC, unsupportedSampleRate);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory( .setEncoderFactory(
@ -2451,19 +2445,14 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
assertThat(result.exportResult.sampleRate).isEqualTo(fallbackSampleRate); // The original clip is 1 second long.
assertThat(result.exportResult.durationMs).isWithin(50).of(1_000); assertThat(result.exportResult.durationMs).isWithin(50).of(1_000);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }
@Test @Test
@Ignore("TODO: b/389068218 - Fix this test and re-enable it") public void export_withMultipleHighSampleRatesAndFallbackEnabled_exportsWithCorrectDuration()
public void
export_withTwoUnsupportedAndOneSupportedSampleRateAndFallbackEnabled_exportsWithFallbackSampleRate()
throws Exception { throws Exception {
int unsupportedSampleRate = 192_000;
int fallbackSampleRate =
getFallbackAssumingUnsupportedSampleRate(MimeTypes.AUDIO_AAC, unsupportedSampleRate);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory( .setEncoderFactory(
@ -2488,7 +2477,7 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, composition); .run(testId, composition);
assertThat(result.exportResult.sampleRate).isEqualTo(fallbackSampleRate); // Each original clip is 1 second long.
assertThat(result.exportResult.durationMs).isWithin(150).of(3_000); assertThat(result.exportResult.durationMs).isWithin(150).of(3_000);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }

View File

@ -98,7 +98,7 @@ import org.checkerframework.dataflow.qual.Pure;
muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO))) muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO)))
.build()); .build());
AudioFormat actualEncoderAudioFormat = new AudioFormat(encoder.getConfigurationFormat()); AudioFormat actualEncoderAudioFormat = new AudioFormat(encoder.getInputFormat());
// This occurs when the encoder does not support the requested format. In this case, the audio // This occurs when the encoder does not support the requested format. In this case, the audio
// graph output needs to be resampled to a sample rate matching the encoder input to avoid // graph output needs to be resampled to a sample rate matching the encoder input to avoid
// distorted audio. // distorted audio.