Allow output audio MIME type to be set in TranscodingTransformer.

This introduces a new option `setAudioMimeType` in `TranscodingTransformer.Builder` and a corresponding check whether the selected type is supported. This check is done using `supportsSampleMimeType` which is now part of the `Muxer.Factory` and `MuxerWrapper` rather than `Muxer`.
A new field `audioMimeType` is added to `Transformation` and the `TransformerAudioRenderer` uses this instead of the input MIME type if requested.

PiperOrigin-RevId: 405367817
This commit is contained in:
olly 2021-10-25 12:28:46 +01:00 committed by Oliver Woodman
parent 17d2f5a0b1
commit a42d9f36b1
10 changed files with 144 additions and 71 deletions

View File

@ -42,7 +42,7 @@ import java.nio.ByteBuffer;
@Override
public FrameworkMuxer create(String path, String outputMimeType) throws IOException {
MediaMuxer mediaMuxer = new MediaMuxer(path, mimeTypeToMuxerOutputFormat(outputMimeType));
return new FrameworkMuxer(mediaMuxer, outputMimeType);
return new FrameworkMuxer(mediaMuxer);
}
@RequiresApi(26)
@ -53,7 +53,7 @@ import java.nio.ByteBuffer;
new MediaMuxer(
parcelFileDescriptor.getFileDescriptor(),
mimeTypeToMuxerOutputFormat(outputMimeType));
return new FrameworkMuxer(mediaMuxer, outputMimeType);
return new FrameworkMuxer(mediaMuxer);
}
@Override
@ -65,47 +65,46 @@ import java.nio.ByteBuffer;
}
return true;
}
@Override
public boolean supportsSampleMimeType(
@Nullable String sampleMimeType, String containerMimeType) {
// MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat).
boolean isAudio = MimeTypes.isAudio(sampleMimeType);
boolean isVideo = MimeTypes.isVideo(sampleMimeType);
if (containerMimeType.equals(MimeTypes.VIDEO_MP4)) {
if (isVideo) {
return MimeTypes.VIDEO_H263.equals(sampleMimeType)
|| MimeTypes.VIDEO_H264.equals(sampleMimeType)
|| MimeTypes.VIDEO_MP4V.equals(sampleMimeType)
|| (Util.SDK_INT >= 24 && MimeTypes.VIDEO_H265.equals(sampleMimeType));
} else if (isAudio) {
return MimeTypes.AUDIO_AAC.equals(sampleMimeType)
|| MimeTypes.AUDIO_AMR_NB.equals(sampleMimeType)
|| MimeTypes.AUDIO_AMR_WB.equals(sampleMimeType);
}
} else if (containerMimeType.equals(MimeTypes.VIDEO_WEBM) && SDK_INT >= 21) {
if (isVideo) {
return MimeTypes.VIDEO_VP8.equals(sampleMimeType)
|| (Util.SDK_INT >= 24 && MimeTypes.VIDEO_VP9.equals(sampleMimeType));
} else if (isAudio) {
return MimeTypes.AUDIO_VORBIS.equals(sampleMimeType);
}
}
return false;
}
}
private final MediaMuxer mediaMuxer;
private final String outputMimeType;
private final MediaCodec.BufferInfo bufferInfo;
private boolean isStarted;
private FrameworkMuxer(MediaMuxer mediaMuxer, String outputMimeType) {
private FrameworkMuxer(MediaMuxer mediaMuxer) {
this.mediaMuxer = mediaMuxer;
this.outputMimeType = outputMimeType;
bufferInfo = new MediaCodec.BufferInfo();
}
@Override
public boolean supportsSampleMimeType(@Nullable String mimeType) {
// MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat).
boolean isAudio = MimeTypes.isAudio(mimeType);
boolean isVideo = MimeTypes.isVideo(mimeType);
if (outputMimeType.equals(MimeTypes.VIDEO_MP4)) {
if (isVideo) {
return MimeTypes.VIDEO_H263.equals(mimeType)
|| MimeTypes.VIDEO_H264.equals(mimeType)
|| MimeTypes.VIDEO_MP4V.equals(mimeType)
|| (Util.SDK_INT >= 24 && MimeTypes.VIDEO_H265.equals(mimeType));
} else if (isAudio) {
return MimeTypes.AUDIO_AAC.equals(mimeType)
|| MimeTypes.AUDIO_AMR_NB.equals(mimeType)
|| MimeTypes.AUDIO_AMR_WB.equals(mimeType);
}
} else if (outputMimeType.equals(MimeTypes.VIDEO_WEBM) && SDK_INT >= 21) {
if (isVideo) {
return MimeTypes.VIDEO_VP8.equals(mimeType)
|| (Util.SDK_INT >= 24 && MimeTypes.VIDEO_VP9.equals(mimeType));
} else if (isAudio) {
return MimeTypes.AUDIO_VORBIS.equals(mimeType);
}
}
return false;
}
@Override
public int addTrack(Format format) {
String sampleMimeType = checkNotNull(format.sampleMimeType);

View File

@ -25,11 +25,12 @@ import java.nio.ByteBuffer;
/**
* Abstracts media muxing operations.
*
* <p>Query whether {@link #supportsSampleMimeType(String) sample MIME types are supported} and
* {@link #addTrack(Format) add all tracks}, then {@link #writeSampleData(int, ByteBuffer, boolean,
* long) write sample data} to mux samples. Once any sample data has been written, it is not
* possible to add tracks. After writing all sample data, {@link #release(boolean) release} the
* instance to finish writing to the output and return any resources to the system.
* <p>Query whether {@link Factory#supportsOutputMimeType(String) container MIME type} and {@link
* Factory#supportsSampleMimeType(String, String) sample MIME types} are supported and {@link
* #addTrack(Format) add all tracks}, then {@link #writeSampleData(int, ByteBuffer, boolean, long)
* write sample data} to mux samples. Once any sample data has been written, it is not possible to
* add tracks. After writing all sample data, {@link #release(boolean) release} the instance to
* finish writing to the output and return any resources to the system.
*/
/* package */ interface Muxer {
@ -62,10 +63,13 @@ import java.nio.ByteBuffer;
/** Returns whether the {@link MimeTypes MIME type} provided is a supported output format. */
boolean supportsOutputMimeType(String mimeType);
}
/** Returns whether the sample {@link MimeTypes MIME type} is supported. */
boolean supportsSampleMimeType(@Nullable String mimeType);
/**
* Returns whether the sample {@link MimeTypes MIME type} is supported with the given container
* {@link MimeTypes MIME type}.
*/
boolean supportsSampleMimeType(@Nullable String sampleMimeType, String containerMimeType);
}
/**
* Adds a track with the specified format, and returns its index (to be passed in subsequent calls

View File

@ -45,8 +45,10 @@ import java.nio.ByteBuffer;
private static final long MAX_TRACK_WRITE_AHEAD_US = C.msToUs(500);
private final Muxer muxer;
private final Muxer.Factory muxerFactory;
private final SparseIntArray trackTypeToIndex;
private final SparseLongArray trackTypeToTimeUs;
private final String containerMimeType;
private int trackCount;
private int trackFormatCount;
@ -54,8 +56,10 @@ import java.nio.ByteBuffer;
private @C.TrackType int previousTrackType;
private long minTrackTimeUs;
public MuxerWrapper(Muxer muxer) {
public MuxerWrapper(Muxer muxer, Muxer.Factory muxerFactory, String containerMimeType) {
this.muxer = muxer;
this.muxerFactory = muxerFactory;
this.containerMimeType = containerMimeType;
trackTypeToIndex = new SparseIntArray();
trackTypeToTimeUs = new SparseLongArray();
previousTrackType = C.TRACK_TYPE_NONE;
@ -78,7 +82,7 @@ import java.nio.ByteBuffer;
/** Returns whether the sample {@link MimeTypes MIME type} is supported. */
public boolean supportsSampleMimeType(@Nullable String mimeType) {
return muxer.supportsSampleMimeType(mimeType);
return muxerFactory.supportsSampleMimeType(mimeType, containerMimeType);
}
/**

View File

@ -100,6 +100,7 @@ public final class TranscodingTransformer {
private boolean removeVideo;
private boolean flattenForSlowMotion;
private String outputMimeType;
@Nullable private String audioMimeType;
private TranscodingTransformer.Listener listener;
private Looper looper;
private Clock clock;
@ -122,6 +123,7 @@ public final class TranscodingTransformer {
this.removeVideo = transcodingTransformer.transformation.removeVideo;
this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion;
this.outputMimeType = transcodingTransformer.transformation.outputMimeType;
this.audioMimeType = transcodingTransformer.transformation.audioMimeType;
this.listener = transcodingTransformer.listener;
this.looper = transcodingTransformer.looper;
this.clock = transcodingTransformer.clock;
@ -212,10 +214,8 @@ public final class TranscodingTransformer {
}
/**
* Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. The
* output MIME type should be supported by the {@link
* Muxer.Factory#supportsOutputMimeType(String) muxer}. Values supported by the default {@link
* FrameworkMuxer} are:
* Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported
* values are:
*
* <ul>
* <li>{@link MimeTypes#VIDEO_MP4}
@ -230,6 +230,31 @@ public final class TranscodingTransformer {
return this;
}
/**
* Sets the audio MIME type of the output. The default value is to use the same MIME type as the
* input. Supported values are:
*
* <ul>
* <li>when the container MIME type is {@link MimeTypes#VIDEO_MP4}:
* <ul>
* <li>{@link MimeTypes#AUDIO_AAC}
* <li>{@link MimeTypes#AUDIO_AMR_NB}
* <li>{@link MimeTypes#AUDIO_AMR_WB}
* </ul>
* <li>when the container MIME type is {@link MimeTypes#VIDEO_WEBM}:
* <ul>
* <li>{@link MimeTypes#AUDIO_VORBIS}
* </ul>
* </ul>
*
* @param audioMimeType The MIME type of the audio samples in the output.
* @return This builder.
*/
public Builder setAudioMimeType(String audioMimeType) {
this.audioMimeType = audioMimeType;
return this;
}
/**
* Sets the {@link TranscodingTransformer.Listener} to listen to the transformation events.
*
@ -290,6 +315,7 @@ public final class TranscodingTransformer {
* @throws IllegalStateException If both audio and video have been removed (otherwise the output
* would not contain any samples).
* @throws IllegalStateException If the muxer doesn't support the requested output MIME type.
* @throws IllegalStateException If the muxer doesn't support the requested audio MIME type.
*/
public TranscodingTransformer build() {
checkStateNotNull(context);
@ -303,8 +329,17 @@ public final class TranscodingTransformer {
checkState(
muxerFactory.supportsOutputMimeType(outputMimeType),
"Unsupported output MIME type: " + outputMimeType);
if (audioMimeType != null) {
checkState(
muxerFactory.supportsSampleMimeType(audioMimeType, outputMimeType),
"Unsupported sample MIME type "
+ audioMimeType
+ " for container MIME type "
+ outputMimeType);
}
Transformation transformation =
new Transformation(removeAudio, removeVideo, flattenForSlowMotion, outputMimeType);
new Transformation(
removeAudio, removeVideo, flattenForSlowMotion, outputMimeType, audioMimeType);
return new TranscodingTransformer(
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
}
@ -469,7 +504,8 @@ public final class TranscodingTransformer {
throw new IllegalStateException("There is already a transformation in progress.");
}
MuxerWrapper muxerWrapper = new MuxerWrapper(muxer);
MuxerWrapper muxerWrapper =
new MuxerWrapper(muxer, muxerFactory, transformation.outputMimeType);
this.muxerWrapper = muxerWrapper;
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
trackSelector.setParameters(

View File

@ -16,6 +16,8 @@
package com.google.android.exoplayer2.transformer;
import androidx.annotation.Nullable;
/** A media transformation configuration. */
/* package */ final class Transformation {
@ -23,15 +25,18 @@ package com.google.android.exoplayer2.transformer;
public final boolean removeVideo;
public final boolean flattenForSlowMotion;
public final String outputMimeType;
@Nullable public final String audioMimeType;
public Transformation(
boolean removeAudio,
boolean removeVideo,
boolean flattenForSlowMotion,
String outputMimeType) {
String outputMimeType,
@Nullable String audioMimeType) {
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
this.flattenForSlowMotion = flattenForSlowMotion;
this.outputMimeType = outputMimeType;
this.audioMimeType = audioMimeType;
}
}

View File

@ -209,10 +209,8 @@ public final class Transformer {
}
/**
* Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. The
* output MIME type should be supported by the {@link
* Muxer.Factory#supportsOutputMimeType(String) muxer}. Values supported by the default {@link
* FrameworkMuxer} are:
* Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported
* values are:
*
* <ul>
* <li>{@link MimeTypes#VIDEO_MP4}
@ -301,7 +299,12 @@ public final class Transformer {
muxerFactory.supportsOutputMimeType(outputMimeType),
"Unsupported output MIME type: " + outputMimeType);
Transformation transformation =
new Transformation(removeAudio, removeVideo, flattenForSlowMotion, outputMimeType);
new Transformation(
removeAudio,
removeVideo,
flattenForSlowMotion,
outputMimeType,
/* audioMimeType= */ null);
return new Transformer(
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
}
@ -464,7 +467,8 @@ public final class Transformer {
throw new IllegalStateException("There is already a transformation in progress.");
}
MuxerWrapper muxerWrapper = new MuxerWrapper(muxer);
MuxerWrapper muxerWrapper =
new MuxerWrapper(muxer, muxerFactory, transformation.outputMimeType);
this.muxerWrapper = muxerWrapper;
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
trackSelector.setParameters(

View File

@ -350,11 +350,15 @@ import java.nio.ByteBuffer;
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
}
}
String audioMimeType =
transformation.audioMimeType == null
? checkNotNull(inputFormat).sampleMimeType
: transformation.audioMimeType;
try {
encoder =
MediaCodecAdapterWrapper.createForAudioEncoding(
new Format.Builder()
.setSampleMimeType(checkNotNull(inputFormat).sampleMimeType)
.setSampleMimeType(audioMimeType)
.setSampleRate(outputAudioFormat.sampleRate)
.setChannelCount(outputAudioFormat.channelCount)
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)

View File

@ -52,7 +52,13 @@ import com.google.android.exoplayer2.util.MimeTypes;
@Nullable String sampleMimeType = format.sampleMimeType;
if (MimeTypes.getTrackType(sampleMimeType) != getTrackType()) {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
} else if (muxerWrapper.supportsSampleMimeType(sampleMimeType)) {
} else if ((MimeTypes.isAudio(sampleMimeType)
&& muxerWrapper.supportsSampleMimeType(
transformation.audioMimeType == null
? sampleMimeType
: transformation.audioMimeType))
|| (MimeTypes.isVideo(sampleMimeType)
&& muxerWrapper.supportsSampleMimeType(sampleMimeType))) {
return RendererCapabilities.create(C.FORMAT_HANDLED);
} else {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE);

View File

@ -26,30 +26,27 @@ import java.util.List;
/**
* An implementation of {@link Muxer} that supports dumping information about all interactions (for
* testing purposes) and delegates the actual muxing operations to a {@link FrameworkMuxer}.
* testing purposes) and delegates the actual muxing operations to another {@link Muxer} created
* using the factory provided.
*/
public final class TestMuxer implements Muxer, Dumper.Dumpable {
private final Muxer frameworkMuxer;
private final Muxer muxer;
private final List<Dumper.Dumpable> dumpables;
/** Creates a new test muxer. */
public TestMuxer(String path, String outputMimeType) throws IOException {
frameworkMuxer = new FrameworkMuxer.Factory().create(path, outputMimeType);
public TestMuxer(String path, String outputMimeType, Muxer.Factory muxerFactory)
throws IOException {
muxer = muxerFactory.create(path, outputMimeType);
dumpables = new ArrayList<>();
dumpables.add(dumper -> dumper.add("containerMimeType", outputMimeType));
}
// Muxer implementation.
@Override
public boolean supportsSampleMimeType(String mimeType) {
return frameworkMuxer.supportsSampleMimeType(mimeType);
}
@Override
public int addTrack(Format format) {
int trackIndex = frameworkMuxer.addTrack(format);
int trackIndex = muxer.addTrack(format);
dumpables.add(new DumpableFormat(format, trackIndex));
return trackIndex;
}
@ -58,13 +55,13 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable {
public void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) {
dumpables.add(new DumpableSample(trackIndex, data, isKeyFrame, presentationTimeUs));
frameworkMuxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs);
muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs);
}
@Override
public void release(boolean forCancellation) {
dumpables.add(dumper -> dumper.add("released", true));
frameworkMuxer.release(forCancellation);
muxer.release(forCancellation);
}
// Dumper.Dumpable implementation.

View File

@ -562,16 +562,25 @@ public final class TransformerTest {
}
private final class TestMuxerFactory implements Muxer.Factory {
private final Muxer.Factory frameworkMuxerFactory;
public TestMuxerFactory() {
frameworkMuxerFactory = new FrameworkMuxer.Factory();
}
@Override
public Muxer create(String path, String outputMimeType) throws IOException {
testMuxer = new TestMuxer(path, outputMimeType);
testMuxer = new TestMuxer(path, outputMimeType, frameworkMuxerFactory);
return testMuxer;
}
@Override
public Muxer create(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType)
throws IOException {
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), outputMimeType);
testMuxer =
new TestMuxer(
"FD:" + parcelFileDescriptor.getFd(), outputMimeType, frameworkMuxerFactory);
return testMuxer;
}
@ -579,5 +588,10 @@ public final class TransformerTest {
public boolean supportsOutputMimeType(String mimeType) {
return true;
}
@Override
public boolean supportsSampleMimeType(String sampleMimeType, String outputMimeType) {
return frameworkMuxerFactory.supportsSampleMimeType(sampleMimeType, outputMimeType);
}
}
}