Add codec names to TransformationResult

PiperOrigin-RevId: 494736085
This commit is contained in:
samrobinson 2022-12-12 16:28:35 +00:00 committed by Ian Baker
parent 36e52691bb
commit 6c7e892d2f
6 changed files with 255 additions and 95 deletions

View File

@ -144,6 +144,18 @@ public class TransformationTestResult {
if (transformationResult.videoFrameCount > 0) {
jsonObject.put("videoFrameCount", transformationResult.videoFrameCount);
}
if (transformationResult.audioDecoderName != null) {
jsonObject.put("audioDecoderName", transformationResult.audioDecoderName);
}
if (transformationResult.videoDecoderName != null) {
jsonObject.put("videoDecoderName", transformationResult.videoDecoderName);
}
if (transformationResult.audioEncoderName != null) {
jsonObject.put("audioEncoderName", transformationResult.audioEncoderName);
}
if (transformationResult.videoEncoderName != null) {
jsonObject.put("videoEncoderName", transformationResult.videoEncoderName);
}
if (throughputFps != C.RATE_UNSET) {
jsonObject.put("throughputFps", throughputFps);
}

View File

@ -23,9 +23,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.SystemClock;
@ -155,7 +153,8 @@ public class TransformerAndroidTestRunner {
}
private final Context context;
private final CodecNameForwardingCodecFactory transformerCodecFactory;
private final CapturingDecoderFactory decoderFactory;
private final CapturingEncoderFactory encoderFactory;
private final Transformer transformer;
private final int timeoutSeconds;
private final boolean requestCalculateSsim;
@ -170,13 +169,13 @@ public class TransformerAndroidTestRunner {
boolean suppressAnalysisExceptions,
@Nullable Map<String, Object> inputValues) {
this.context = context;
this.transformerCodecFactory =
new CodecNameForwardingCodecFactory(transformer.decoderFactory, transformer.encoderFactory);
this.decoderFactory = new CapturingDecoderFactory(transformer.decoderFactory);
this.encoderFactory = new CapturingEncoderFactory(transformer.encoderFactory);
this.transformer =
transformer
.buildUpon()
.setDecoderFactory(transformerCodecFactory)
.setEncoderFactory(transformerCodecFactory)
.setDecoderFactory(decoderFactory)
.setEncoderFactory(encoderFactory)
.build();
this.timeoutSeconds = timeoutSeconds;
this.requestCalculateSsim = requestCalculateSsim;
@ -209,7 +208,7 @@ public class TransformerAndroidTestRunner {
resultJson.put("exception", AndroidTestUtil.exceptionAsJsonObject(e));
throw e;
} finally {
resultJson.put("codecDetails", transformerCodecFactory.getCodecNamesAsJsonObject());
resultJson.put("codecDetails", getCodecNamesAsJsonObject());
AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson);
}
}
@ -402,87 +401,20 @@ public class TransformerAndroidTestRunner {
return false;
}
/**
* A {@link Codec.EncoderFactory} that forwards all methods to another encoder factory, whilst
* providing visibility into the names of last codecs created by it.
*/
private static class CodecNameForwardingCodecFactory
implements Codec.DecoderFactory, Codec.EncoderFactory {
/** The name of the last audio {@link Codec decoder} created. */
@Nullable public String audioDecoderName;
/** The name of the last video {@link Codec decoder} created. */
@Nullable public String videoDecoderName;
/** The name of the last audio {@link Codec encoder} created. */
@Nullable public String audioEncoderName;
/** The name of the last video {@link Codec encoder} created. */
@Nullable public String videoEncoderName;
private final Codec.DecoderFactory decoderFactory;
private final Codec.EncoderFactory encoderFactory;
public CodecNameForwardingCodecFactory(
Codec.DecoderFactory decoderFactory, Codec.EncoderFactory encoderFactory) {
this.decoderFactory = decoderFactory;
this.encoderFactory = encoderFactory;
private JSONObject getCodecNamesAsJsonObject() throws JSONException {
JSONObject detailsJson = new JSONObject();
if (decoderFactory.getAudioDecoderName() != null) {
detailsJson.put("audioDecoderName", decoderFactory.getAudioDecoderName());
}
@Override
public Codec createForAudioDecoding(Format format) throws TransformationException {
Codec audioDecoder = decoderFactory.createForAudioDecoding(format);
audioDecoderName = audioDecoder.getName();
return audioDecoder;
if (decoderFactory.getVideoDecoderName() != null) {
detailsJson.put("videoDecoderName", decoderFactory.getVideoDecoderName());
}
@Override
public Codec createForVideoDecoding(
Format format, Surface outputSurface, boolean requestSdrToneMapping)
throws TransformationException {
Codec videoDecoder =
decoderFactory.createForVideoDecoding(format, outputSurface, requestSdrToneMapping);
videoDecoderName = videoDecoder.getName();
return videoDecoder;
if (encoderFactory.getAudioEncoderName() != null) {
detailsJson.put("audioEncoderName", encoderFactory.getAudioEncoderName());
}
@Override
public Codec createForAudioEncoding(Format format) throws TransformationException {
Codec audioEncoder = encoderFactory.createForAudioEncoding(format);
audioEncoderName = audioEncoder.getName();
return audioEncoder;
}
@Override
public Codec createForVideoEncoding(Format format) throws TransformationException {
Codec videoEncoder = encoderFactory.createForVideoEncoding(format);
videoEncoderName = videoEncoder.getName();
return videoEncoder;
}
@Override
public boolean audioNeedsEncoding() {
return encoderFactory.audioNeedsEncoding();
}
@Override
public boolean videoNeedsEncoding() {
return encoderFactory.videoNeedsEncoding();
}
public JSONObject getCodecNamesAsJsonObject() throws JSONException {
JSONObject detailsJson = new JSONObject();
if (audioDecoderName != null) {
detailsJson.put("audioDecoderName", audioDecoderName);
}
if (videoDecoderName != null) {
detailsJson.put("videoDecoderName", videoDecoderName);
}
if (audioEncoderName != null) {
detailsJson.put("audioEncoderName", audioEncoderName);
}
if (videoEncoderName != null) {
detailsJson.put("videoEncoderName", videoEncoderName);
}
return detailsJson;
if (encoderFactory.getVideoEncoderName() != null) {
detailsJson.put("videoEncoderName", encoderFactory.getVideoEncoderName());
}
return detailsJson;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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 androidx.media3.transformer;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
/** A forwarding {@link Codec.DecoderFactory} that captures details about the codecs created. */
/* package */ final class CapturingDecoderFactory implements Codec.DecoderFactory {
private final Codec.DecoderFactory decoderFactory;
@Nullable private String audioDecoderName;
@Nullable private String videoDecoderName;
public CapturingDecoderFactory(Codec.DecoderFactory decoderFactory) {
this.decoderFactory = decoderFactory;
}
@Override
public Codec createForAudioDecoding(Format format) throws TransformationException {
Codec audioDecoder = decoderFactory.createForAudioDecoding(format);
audioDecoderName = audioDecoder.getName();
return audioDecoder;
}
@Override
public Codec createForVideoDecoding(
Format format, Surface outputSurface, boolean requestSdrToneMapping)
throws TransformationException {
Codec videoDecoder =
decoderFactory.createForVideoDecoding(format, outputSurface, requestSdrToneMapping);
videoDecoderName = videoDecoder.getName();
return videoDecoder;
}
/**
* Returns the name of the last audio {@linkplain Codec decoder} created, or {@code null} if none
* were created.
*/
@Nullable
public String getAudioDecoderName() {
return audioDecoderName;
}
/**
* Returns the name of the last video {@linkplain Codec decoder} created, or {@code null} if none
* were created.
*/
@Nullable
public String getVideoDecoderName() {
return videoDecoderName;
}
}

View File

@ -0,0 +1,73 @@
/*
* 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 androidx.media3.transformer;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
/** A forwarding {@link Codec.EncoderFactory} that captures details about the codecs created. */
/* package */ final class CapturingEncoderFactory implements Codec.EncoderFactory {
private final Codec.EncoderFactory encoderFactory;
@Nullable private String audioEncoderName;
@Nullable private String videoEncoderName;
public CapturingEncoderFactory(Codec.EncoderFactory encoderFactory) {
this.encoderFactory = encoderFactory;
}
@Override
public Codec createForAudioEncoding(Format format) throws TransformationException {
Codec audioEncoder = encoderFactory.createForAudioEncoding(format);
audioEncoderName = audioEncoder.getName();
return audioEncoder;
}
@Override
public Codec createForVideoEncoding(Format format) throws TransformationException {
Codec videoEncoder = encoderFactory.createForVideoEncoding(format);
videoEncoderName = videoEncoder.getName();
return videoEncoder;
}
@Override
public boolean audioNeedsEncoding() {
return encoderFactory.audioNeedsEncoding();
}
@Override
public boolean videoNeedsEncoding() {
return encoderFactory.videoNeedsEncoding();
}
/**
* Returns the name of the last audio {@linkplain Codec encoder} created, or {@code null} if none
* were created.
*/
@Nullable
public String getAudioEncoderName() {
return audioEncoderName;
}
/**
* Returns the name of the last video {@linkplain Codec encoder} created, or {@code null} if none
* were created.
*/
@Nullable
public String getVideoEncoderName() {
return videoEncoderName;
}
}

View File

@ -21,8 +21,9 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
/** Information about the result of a successful transformation. */
/** Information about the result of a transformation. */
@UnstableApi
public final class TransformationResult {
@ -33,6 +34,10 @@ public final class TransformationResult {
private int averageAudioBitrate;
private int averageVideoBitrate;
private int videoFrameCount;
@Nullable private String audioDecoderName;
@Nullable private String audioEncoderName;
@Nullable private String videoDecoderName;
@Nullable private String videoEncoderName;
public Builder() {
durationMs = C.TIME_UNSET;
@ -101,9 +106,45 @@ public final class TransformationResult {
return this;
}
/** Sets the name of the audio decoder used. */
@CanIgnoreReturnValue
public Builder setAudioDecoderName(@Nullable String audioDecoderName) {
this.audioDecoderName = audioDecoderName;
return this;
}
/** Sets the name of the audio encoder used. */
@CanIgnoreReturnValue
public Builder setAudioEncoderName(@Nullable String audioEncoderName) {
this.audioEncoderName = audioEncoderName;
return this;
}
/** Sets the name of the video decoder used. */
@CanIgnoreReturnValue
public Builder setVideoDecoderName(@Nullable String videoDecoderName) {
this.videoDecoderName = videoDecoderName;
return this;
}
/** Sets the name of the video encoder used. */
@CanIgnoreReturnValue
public Builder setVideoEncoderName(@Nullable String videoEncoderName) {
this.videoEncoderName = videoEncoderName;
return this;
}
public TransformationResult build() {
return new TransformationResult(
durationMs, fileSizeBytes, averageAudioBitrate, averageVideoBitrate, videoFrameCount);
durationMs,
fileSizeBytes,
averageAudioBitrate,
averageVideoBitrate,
videoFrameCount,
audioDecoderName,
audioEncoderName,
videoDecoderName,
videoEncoderName);
}
}
@ -122,17 +163,34 @@ public final class TransformationResult {
/** The number of video frames. */
public final int videoFrameCount;
/** The name of the audio decoder used, or {@code null} if none were used. */
@Nullable public final String audioDecoderName;
/** The name of the audio encoder used, or {@code null} if none were used. */
@Nullable public final String audioEncoderName;
/** The name of the video decoder used, or {@code null} if none were used. */
@Nullable public final String videoDecoderName;
/** The name of the video encoder used, or {@code null} if none were used. */
@Nullable public final String videoEncoderName;
private TransformationResult(
long durationMs,
long fileSizeBytes,
int averageAudioBitrate,
int averageVideoBitrate,
int videoFrameCount) {
int videoFrameCount,
@Nullable String audioDecoderName,
@Nullable String audioEncoderName,
@Nullable String videoDecoderName,
@Nullable String videoEncoderName) {
this.durationMs = durationMs;
this.fileSizeBytes = fileSizeBytes;
this.averageAudioBitrate = averageAudioBitrate;
this.averageVideoBitrate = averageVideoBitrate;
this.videoFrameCount = videoFrameCount;
this.audioDecoderName = audioDecoderName;
this.audioEncoderName = audioEncoderName;
this.videoDecoderName = videoDecoderName;
this.videoEncoderName = videoEncoderName;
}
public Builder buildUpon() {
@ -141,7 +199,11 @@ public final class TransformationResult {
.setFileSizeBytes(fileSizeBytes)
.setAverageAudioBitrate(averageAudioBitrate)
.setAverageVideoBitrate(averageVideoBitrate)
.setVideoFrameCount(videoFrameCount);
.setVideoFrameCount(videoFrameCount)
.setAudioDecoderName(audioDecoderName)
.setAudioEncoderName(audioEncoderName)
.setVideoDecoderName(videoDecoderName)
.setVideoEncoderName(videoEncoderName);
}
@Override
@ -157,7 +219,11 @@ public final class TransformationResult {
&& fileSizeBytes == result.fileSizeBytes
&& averageAudioBitrate == result.averageAudioBitrate
&& averageVideoBitrate == result.averageVideoBitrate
&& videoFrameCount == result.videoFrameCount;
&& videoFrameCount == result.videoFrameCount
&& Objects.equals(audioDecoderName, result.audioDecoderName)
&& Objects.equals(audioEncoderName, result.audioEncoderName)
&& Objects.equals(videoDecoderName, result.videoDecoderName)
&& Objects.equals(videoEncoderName, result.videoEncoderName);
}
@Override
@ -167,6 +233,10 @@ public final class TransformationResult {
result = 31 * result + averageAudioBitrate;
result = 31 * result + averageVideoBitrate;
result = 31 * result + videoFrameCount;
result = 31 * result + Objects.hashCode(audioDecoderName);
result = 31 * result + Objects.hashCode(audioEncoderName);
result = 31 * result + Objects.hashCode(videoDecoderName);
result = 31 * result + Objects.hashCode(videoEncoderName);
return result;
}
}

View File

@ -94,8 +94,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final ImmutableList<AudioProcessor> audioProcessors;
private final ImmutableList<Effect> videoEffects;
private final boolean forceSilentAudio;
private final Codec.DecoderFactory decoderFactory;
private final Codec.EncoderFactory encoderFactory;
private final CapturingDecoderFactory decoderFactory;
private final CapturingEncoderFactory encoderFactory;
private final FrameProcessor.Factory frameProcessorFactory;
private final Listener listener;
private final DebugViewProvider debugViewProvider;
@ -141,8 +141,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.audioProcessors = audioProcessors;
this.videoEffects = videoEffects;
this.forceSilentAudio = forceSilentAudio;
this.decoderFactory = decoderFactory;
this.encoderFactory = encoderFactory;
this.decoderFactory = new CapturingDecoderFactory(decoderFactory);
this.encoderFactory = new CapturingEncoderFactory(encoderFactory);
this.frameProcessorFactory = frameProcessorFactory;
this.listener = listener;
this.debugViewProvider = debugViewProvider;
@ -316,6 +316,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
for (int i = 0; i < samplePipelines.size(); i++) {
samplePipelines.get(i).release();
}
// TODO(b/250564186): Create TransformationResult on END_REASON_ERROR as well.
if (endReason == END_REASON_COMPLETED) {
transformationResult =
new TransformationResult.Builder()
@ -326,6 +328,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_VIDEO))
.setVideoFrameCount(muxerWrapper.getTrackSampleCount(C.TRACK_TYPE_VIDEO))
.setFileSizeBytes(muxerWrapper.getCurrentOutputSizeBytes())
.setAudioDecoderName(decoderFactory.getAudioDecoderName())
.setAudioEncoderName(encoderFactory.getAudioEncoderName())
.setVideoDecoderName(decoderFactory.getVideoDecoderName())
.setVideoEncoderName(encoderFactory.getVideoEncoderName())
.build();
}
} finally {