mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
17d2f5a0b1
commit
a42d9f36b1
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user