Switch transformer tests to use dump files

Add an interface to muxers to allow tests to pass a dumpable muxer.

#minor-release

PiperOrigin-RevId: 354543388
This commit is contained in:
andrewlewis 2021-01-29 17:02:25 +00:00 committed by Oliver Woodman
parent 2b24e88726
commit afb41123c2
17 changed files with 3052 additions and 302 deletions

View file

@ -0,0 +1,188 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.SDK_INT;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.ParcelFileDescriptor;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
/** Muxer implementation that uses a {@link MediaMuxer}. */
@RequiresApi(18)
/* package */ final class FrameworkMuxer implements Muxer {
public static final class Factory implements Muxer.Factory {
@Override
public FrameworkMuxer create(String path, String outputMimeType) throws IOException {
MediaMuxer mediaMuxer = new MediaMuxer(path, mimeTypeToMuxerOutputFormat(outputMimeType));
return new FrameworkMuxer(mediaMuxer, outputMimeType);
}
@RequiresApi(26)
@Override
public FrameworkMuxer create(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType)
throws IOException {
MediaMuxer mediaMuxer =
new MediaMuxer(
parcelFileDescriptor.getFileDescriptor(),
mimeTypeToMuxerOutputFormat(outputMimeType));
return new FrameworkMuxer(mediaMuxer, outputMimeType);
}
@Override
public boolean supportsOutputMimeType(String mimeType) {
try {
mimeTypeToMuxerOutputFormat(mimeType);
} catch (IllegalStateException e) {
return false;
}
return true;
}
}
private final MediaMuxer mediaMuxer;
private final String outputMimeType;
private final MediaCodec.BufferInfo bufferInfo;
private boolean isStarted;
private FrameworkMuxer(MediaMuxer mediaMuxer, String outputMimeType) {
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);
MediaFormat mediaFormat;
if (MimeTypes.isAudio(sampleMimeType)) {
mediaFormat =
MediaFormat.createAudioFormat(
castNonNull(sampleMimeType), format.sampleRate, format.channelCount);
} else {
mediaFormat =
MediaFormat.createVideoFormat(castNonNull(sampleMimeType), format.width, format.height);
mediaMuxer.setOrientationHint(format.rotationDegrees);
}
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
return mediaMuxer.addTrack(mediaFormat);
}
@Override
public void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) {
if (!isStarted) {
isStarted = true;
mediaMuxer.start();
}
int offset = data.position();
int size = data.limit() - offset;
int flags = isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0;
bufferInfo.set(offset, size, presentationTimeUs, flags);
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
}
@Override
public void release() {
if (isStarted) {
isStarted = false;
try {
mediaMuxer.stop();
} catch (IllegalStateException e) {
if (SDK_INT < 30) {
// Set the muxer state to stopped even if mediaMuxer.stop() failed so that
// mediaMuxer.release() doesn't attempt to stop the muxer and therefore doesn't throw the
// same exception without releasing its resources. This is already implemented in
// MediaMuxer
// from API level 30.
try {
Field muxerStoppedStateField = MediaMuxer.class.getDeclaredField("MUXER_STATE_STOPPED");
muxerStoppedStateField.setAccessible(true);
int muxerStoppedState = castNonNull((Integer) muxerStoppedStateField.get(mediaMuxer));
Field muxerStateField = MediaMuxer.class.getDeclaredField("mState");
muxerStateField.setAccessible(true);
muxerStateField.set(mediaMuxer, muxerStoppedState);
} catch (Exception reflectionException) {
// Do nothing.
}
}
throw e;
}
}
mediaMuxer.release();
}
/**
* Converts a {@link MimeTypes MIME type} into a {@link MediaMuxer.OutputFormat MediaMuxer output
* format}.
*
* @param mimeType The {@link MimeTypes MIME type} to convert.
* @return The corresponding {@link MediaMuxer.OutputFormat MediaMuxer output format}.
* @throws IllegalArgumentException If the {@link MimeTypes MIME type} is not supported as output
* format.
*/
private static int mimeTypeToMuxerOutputFormat(String mimeType) {
if (mimeType.equals(MimeTypes.VIDEO_MP4)) {
return MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
} else if (SDK_INT >= 21 && mimeType.equals(MimeTypes.VIDEO_WEBM)) {
return MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
} else {
throw new IllegalArgumentException("Unsupported output MIME type: " + mimeType);
}
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.transformer;
import android.os.ParcelFileDescriptor;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
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() release} the instance to
* finish writing to the output and return any resources to the system.
*/
/* package */ interface Muxer {
/** Factory for muxers. */
interface Factory {
/**
* Returns a new muxer writing to a file.
*
* @param path The path to the output file.
* @param outputMimeType The container {@link MimeTypes MIME type} of the output file.
* @throws IllegalArgumentException If the path is invalid or the MIME type is not supported.
* @throws IOException If an error occurs opening the output file for writing.
*/
Muxer create(String path, String outputMimeType) throws IOException;
/**
* Returns a new muxer writing to a file descriptor.
*
* @param parcelFileDescriptor A readable and writable {@link ParcelFileDescriptor} of the
* output. The file referenced by this ParcelFileDescriptor should not be used before the
* muxer is released. It is the responsibility of the caller to close the
* ParcelFileDescriptor. This can be done after this method returns.
* @param outputMimeType The {@link MimeTypes MIME type} of the output.
* @throws IllegalArgumentException If the file descriptor is invalid or the MIME type is not
* supported.
* @throws IOException If an error occurs opening the output file descriptor for writing.
*/
Muxer create(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType)
throws IOException;
/** 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);
/**
* Adds a track with the specified format, and returns its index (to be passed in subsequent calls
* to {@link #writeSampleData(int, ByteBuffer, boolean, long)}).
*/
int addTrack(Format format);
/**
* Writes the specified sample.
*
* @param trackIndex The index of the track, previously returned by {@link #addTrack(Format)}.
* @param data Buffer containing the sample data to write to the container.
* @param isKeyFrame Whether the sample is a key frame.
* @param presentationTimeUs The presentation time of the sample in microseconds.
*/
void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs);
/** Releases any resources associated with muxing. */
void release();
}

View file

@ -17,25 +17,15 @@
package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.SDK_INT;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static com.google.android.exoplayer2.util.Util.minValue;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.ParcelFileDescriptor;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
/**
@ -54,11 +44,9 @@ import java.nio.ByteBuffer;
*/
private static final long MAX_TRACK_WRITE_AHEAD_US = C.msToUs(500);
private final MediaMuxer mediaMuxer;
private final String outputMimeType;
private final Muxer muxer;
private final SparseIntArray trackTypeToIndex;
private final SparseLongArray trackTypeToTimeUs;
private final MediaCodec.BufferInfo bufferInfo;
private int trackCount;
private int trackFormatCount;
@ -66,45 +54,10 @@ import java.nio.ByteBuffer;
private int previousTrackType;
private long minTrackTimeUs;
/**
* Constructs an instance.
*
* @param path The path to the output file.
* @param outputMimeType The {@link MimeTypes MIME type} of the output.
* @throws IllegalArgumentException If the path is invalid or the MIME type is not supported.
* @throws IOException If an error occurs opening the output file for writing.
*/
public MuxerWrapper(String path, String outputMimeType) throws IOException {
this(new MediaMuxer(path, mimeTypeToMuxerOutputFormat(outputMimeType)), outputMimeType);
}
/**
* Constructs an instance.
*
* @param parcelFileDescriptor A readable and writable {@link ParcelFileDescriptor} of the output.
* The file referenced by this ParcelFileDescriptor should not be used before the muxer is
* released. It is the responsibility of the caller to close the ParcelFileDescriptor. This
* can be done after this constructor returns.
* @param outputMimeType The {@link MimeTypes MIME type} of the output.
* @throws IllegalArgumentException If the file descriptor is invalid or the MIME type is not
* supported.
* @throws IOException If an error occurs opening the output file for writing.
*/
@RequiresApi(26)
public MuxerWrapper(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType)
throws IOException {
this(
new MediaMuxer(
parcelFileDescriptor.getFileDescriptor(), mimeTypeToMuxerOutputFormat(outputMimeType)),
outputMimeType);
}
private MuxerWrapper(MediaMuxer mediaMuxer, String outputMimeType) {
this.mediaMuxer = mediaMuxer;
this.outputMimeType = outputMimeType;
public MuxerWrapper(Muxer muxer) {
this.muxer = muxer;
trackTypeToIndex = new SparseIntArray();
trackTypeToTimeUs = new SparseLongArray();
bufferInfo = new MediaCodec.BufferInfo();
previousTrackType = C.TRACK_TYPE_NONE;
}
@ -123,6 +76,11 @@ import java.nio.ByteBuffer;
trackCount++;
}
/** Returns whether the sample {@link MimeTypes MIME type} is supported. */
public boolean supportsSampleMimeType(@Nullable String mimeType) {
return muxer.supportsSampleMimeType(mimeType);
}
/**
* Adds a track format to the muxer.
*
@ -131,9 +89,8 @@ import java.nio.ByteBuffer;
* long) written}.
*
* @param format The {@link Format} to be added.
* @throws IllegalArgumentException If the format is invalid.
* @throws IllegalStateException If the format is unsupported, if there is already a track format
* of the same type (audio or video) or if the muxer is in the wrong state.
* @throws IllegalStateException If the format is unsupported or if there is already a track
* format of the same type (audio or video).
*/
public void addTrackFormat(Format format) {
checkState(trackCount > 0, "All tracks should be registered before the formats are added.");
@ -147,23 +104,11 @@ import java.nio.ByteBuffer;
trackTypeToIndex.get(trackType, /* valueIfKeyNotFound= */ C.INDEX_UNSET) == C.INDEX_UNSET,
"There is already a track of type " + trackType);
MediaFormat mediaFormat;
if (isAudio) {
mediaFormat =
MediaFormat.createAudioFormat(
castNonNull(sampleMimeType), format.sampleRate, format.channelCount);
} else {
mediaFormat =
MediaFormat.createVideoFormat(castNonNull(sampleMimeType), format.width, format.height);
mediaMuxer.setOrientationHint(format.rotationDegrees);
}
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
int trackIndex = mediaMuxer.addTrack(mediaFormat);
int trackIndex = muxer.addTrack(format);
trackTypeToIndex.put(trackType, trackIndex);
trackTypeToTimeUs.put(trackType, 0L);
trackFormatCount++;
if (trackFormatCount == trackCount) {
mediaMuxer.start();
isReady = true;
}
}
@ -180,9 +125,8 @@ import java.nio.ByteBuffer;
* {@link #addTrackFormat(Format) received a format} for every {@link #registerTrack()
* registered track}, or if it should write samples of other track types first to ensure a
* good interleaving.
* @throws IllegalArgumentException If the sample in {@code buffer} is invalid.
* @throws IllegalStateException If the muxer doesn't have any {@link #endTrack(int) non-ended}
* track of the given track type or if the muxer is in the wrong state.
* track of the given track type.
*/
public boolean writeSample(
int trackType, @Nullable ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) {
@ -197,11 +141,7 @@ import java.nio.ByteBuffer;
return true;
}
int offset = data.position();
int size = data.limit() - offset;
int flags = isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0;
bufferInfo.set(offset, size, presentationTimeUs, flags);
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs);
trackTypeToTimeUs.put(trackType, presentationTimeUs);
previousTrackType = trackType;
return true;
@ -222,35 +162,10 @@ import java.nio.ByteBuffer;
* Stops the muxer.
*
* <p>The muxer cannot be used anymore once it is stopped.
*
* @throws IllegalStateException If the muxer is in the wrong state (for example if it didn't
* receive any samples).
*/
public void stop() {
if (!isReady) {
return;
}
isReady = false;
try {
mediaMuxer.stop();
} catch (IllegalStateException e) {
if (SDK_INT < 30) {
// Set the muxer state to stopped even if mediaMuxer.stop() failed so that
// mediaMuxer.release() doesn't attempt to stop the muxer and therefore doesn't throw the
// same exception without releasing its resources. This is already implemented in MediaMuxer
// from API level 30.
try {
Field muxerStoppedStateField = MediaMuxer.class.getDeclaredField("MUXER_STATE_STOPPED");
muxerStoppedStateField.setAccessible(true);
int muxerStoppedState = castNonNull((Integer) muxerStoppedStateField.get(mediaMuxer));
Field muxerStateField = MediaMuxer.class.getDeclaredField("mState");
muxerStateField.setAccessible(true);
muxerStateField.set(mediaMuxer, muxerStoppedState);
} catch (Exception reflectionException) {
// Do nothing.
}
}
throw e;
if (isReady) {
isReady = false;
}
}
@ -261,7 +176,7 @@ import java.nio.ByteBuffer;
*/
public void release() {
isReady = false;
mediaMuxer.release();
muxer.release();
}
/** Returns the number of {@link #registerTrack() registered} tracks. */
@ -269,48 +184,6 @@ import java.nio.ByteBuffer;
return trackCount;
}
/**
* Returns whether the sample {@link MimeTypes MIME type} is supported.
*
* <p>Supported sample formats are documented in {@link MediaMuxer#addTrack(MediaFormat)}.
*/
public boolean supportsSampleMimeType(@Nullable String mimeType) {
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;
}
/**
* Returns whether the {@link MimeTypes MIME type} provided is a supported muxer output format.
*/
public static boolean supportsOutputMimeType(String mimeType) {
try {
mimeTypeToMuxerOutputFormat(mimeType);
} catch (IllegalStateException e) {
return false;
}
return true;
}
/**
* Returns whether the muxer can write a sample of the given track type.
*
@ -337,22 +210,4 @@ import java.nio.ByteBuffer;
return trackTimeUs - minTrackTimeUs <= MAX_TRACK_WRITE_AHEAD_US;
}
/**
* Converts a {@link MimeTypes MIME type} into a {@link MediaMuxer.OutputFormat MediaMuxer output
* format}.
*
* @param mimeType The {@link MimeTypes MIME type} to convert.
* @return The corresponding {@link MediaMuxer.OutputFormat MediaMuxer output format}.
* @throws IllegalArgumentException If the {@link MimeTypes MIME type} is not supported as output
* format.
*/
private static int mimeTypeToMuxerOutputFormat(String mimeType) {
if (mimeType.equals(MimeTypes.VIDEO_MP4)) {
return MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
} else if (SDK_INT >= 21 && mimeType.equals(MimeTypes.VIDEO_WEBM)) {
return MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
} else {
throw new IllegalArgumentException("Unsupported output MIME type: " + mimeType);
}
}
}

View file

@ -79,7 +79,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* of the application's main thread is used. In all cases the Looper of the thread from which the
* transformer must be accessed can be queried using {@link #getApplicationLooper()}.
*/
@RequiresApi(18)
public final class Transformer {
@ -88,6 +87,7 @@ public final class Transformer {
private @MonotonicNonNull Context context;
private @MonotonicNonNull MediaSourceFactory mediaSourceFactory;
private Muxer.Factory muxerFactory;
private boolean removeAudio;
private boolean removeVideo;
private boolean flattenForSlowMotion;
@ -98,6 +98,7 @@ public final class Transformer {
/** Creates a builder with default values. */
public Builder() {
muxerFactory = new FrameworkMuxer.Factory();
outputMimeType = MimeTypes.VIDEO_MP4;
listener = new Listener() {};
looper = Util.getCurrentOrMainLooper();
@ -108,6 +109,7 @@ public final class Transformer {
private Builder(Transformer transformer) {
this.context = transformer.context;
this.mediaSourceFactory = transformer.mediaSourceFactory;
this.muxerFactory = transformer.muxerFactory;
this.removeAudio = transformer.transformation.removeAudio;
this.removeVideo = transformer.transformation.removeVideo;
this.flattenForSlowMotion = transformer.transformation.flattenForSlowMotion;
@ -212,12 +214,8 @@ public final class Transformer {
*
* @param outputMimeType The MIME type of the output.
* @return This builder.
* @throws IllegalArgumentException If the MIME type is not supported.
*/
public Builder setOutputMimeType(String outputMimeType) {
if (!MuxerWrapper.supportsOutputMimeType(outputMimeType)) {
throw new IllegalArgumentException("Unsupported output MIME type: " + outputMimeType);
}
this.outputMimeType = outputMimeType;
return this;
}
@ -262,12 +260,25 @@ public final class Transformer {
return this;
}
/**
* Sets the factory for muxers that write the media container.
*
* @param muxerFactory A {@link Muxer.Factory}.
* @return This builder.
*/
@VisibleForTesting
/* package */ Builder setMuxerFactory(Muxer.Factory muxerFactory) {
this.muxerFactory = muxerFactory;
return this;
}
/**
* Builds a {@link Transformer} instance.
*
* @throws IllegalStateException If the {@link Context} has not been provided.
* @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.
*/
public Transformer build() {
checkStateNotNull(context);
@ -278,9 +289,13 @@ public final class Transformer {
}
mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
}
checkState(
muxerFactory.supportsOutputMimeType(outputMimeType),
"Unsupported output MIME type: " + outputMimeType);
Transformation transformation =
new Transformation(removeAudio, removeVideo, flattenForSlowMotion, outputMimeType);
return new Transformer(context, mediaSourceFactory, transformation, listener, looper, clock);
return new Transformer(
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
}
}
@ -332,6 +347,7 @@ public final class Transformer {
private final Context context;
private final MediaSourceFactory mediaSourceFactory;
private final Muxer.Factory muxerFactory;
private final Transformation transformation;
private final Looper looper;
private final Clock clock;
@ -344,6 +360,7 @@ public final class Transformer {
private Transformer(
Context context,
MediaSourceFactory mediaSourceFactory,
Muxer.Factory muxerFactory,
Transformation transformation,
Transformer.Listener listener,
Looper looper,
@ -353,6 +370,7 @@ public final class Transformer {
"Audio and video cannot both be removed.");
this.context = context;
this.mediaSourceFactory = mediaSourceFactory;
this.muxerFactory = muxerFactory;
this.transformation = transformation;
this.listener = listener;
this.looper = looper;
@ -397,7 +415,7 @@ public final class Transformer {
* @throws IOException If an error occurs opening the output file for writing.
*/
public void startTransformation(MediaItem mediaItem, String path) throws IOException {
startTransformation(mediaItem, new MuxerWrapper(path, transformation.outputMimeType));
startTransformation(mediaItem, muxerFactory.create(path, transformation.outputMimeType));
}
/**
@ -427,17 +445,17 @@ public final class Transformer {
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor)
throws IOException {
startTransformation(
mediaItem, new MuxerWrapper(parcelFileDescriptor, transformation.outputMimeType));
mediaItem, muxerFactory.create(parcelFileDescriptor, transformation.outputMimeType));
}
private void startTransformation(MediaItem mediaItem, MuxerWrapper muxerWrapper) {
private void startTransformation(MediaItem mediaItem, Muxer muxer) {
verifyApplicationThread();
if (player != null) {
throw new IllegalStateException("There is already a transformation in progress.");
}
MuxerWrapper muxerWrapper = new MuxerWrapper(muxer);
this.muxerWrapper = muxerWrapper;
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
trackSelector.setParameters(
new DefaultTrackSelector.ParametersBuilder(context)

View file

@ -50,7 +50,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
@C.FormatSupport
public final int supportsFormat(Format format) {
@Nullable String sampleMimeType = format.sampleMimeType;
if (MimeTypes.getTrackType(format.sampleMimeType) != getTrackType()) {
if (MimeTypes.getTrackType(sampleMimeType) != getTrackType()) {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
} else if (muxerWrapper.supportsSampleMimeType(sampleMimeType)) {
return RendererCapabilities.create(C.FORMAT_HANDLED);

View file

@ -0,0 +1,109 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.transformer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.testutil.DumpableFormat;
import com.google.android.exoplayer2.testutil.Dumper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
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}.
*/
public final class TestMuxer implements Muxer, Dumper.Dumpable {
private final Muxer frameworkMuxer;
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);
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);
dumpables.add(new DumpableFormat(format, trackIndex));
return trackIndex;
}
@Override
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);
}
@Override
public void release() {
dumpables.add(dumper -> dumper.add("released", true));
frameworkMuxer.release();
}
// Dumper.Dumpable implementation.
@Override
public void dump(Dumper dumper) {
for (Dumper.Dumpable dumpable : dumpables) {
dumpable.dump(dumper);
}
}
private static final class DumpableSample implements Dumper.Dumpable {
private final int trackIndex;
private final long presentationTimeUs;
private final boolean isKeyFrame;
private final int sampleDataHashCode;
public DumpableSample(
int trackIndex, ByteBuffer sample, boolean isKeyFrame, long presentationTimeUs) {
this.trackIndex = trackIndex;
this.presentationTimeUs = presentationTimeUs;
this.isKeyFrame = isKeyFrame;
int initialPosition = sample.position();
byte[] data = new byte[sample.remaining()];
sample.get(data);
sample.position(initialPosition);
sampleDataHashCode = Arrays.hashCode(data);
}
@Override
public void dump(Dumper dumper) {
dumper
.startBlock("sample")
.add("trackIndex", trackIndex)
.add("dataHashCode", sampleDataHashCode)
.add("isKeyFrame", isKeyFrame)
.add("presentationTimeUs", presentationTimeUs)
.endBlock();
}
}
}

View file

@ -32,8 +32,8 @@ public class TransformerBuilderTest {
@Test
public void setOutputMimeType_unsupportedMimeType_throws() {
assertThrows(
IllegalArgumentException.class,
() -> new Transformer.Builder().setOutputMimeType(MimeTypes.VIDEO_FLV));
IllegalStateException.class,
() -> new Transformer.Builder().setOutputMimeType(MimeTypes.VIDEO_FLV).build());
}
@Test

View file

@ -28,15 +28,16 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.Iterables;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@ -56,18 +57,20 @@ import org.robolectric.shadows.ShadowMediaCodec;
@RunWith(AndroidJUnit4.class)
public final class TransformerTest {
private static final String FILE_VIDEO_ONLY = "asset:///media/mkv/sample.mkv";
private static final String FILE_AUDIO_ONLY = "asset:///media/amr/sample_nb.amr";
private static final String FILE_AUDIO_VIDEO = "asset:///media/mp4/sample.mp4";
// The ShadowMediaMuxer only outputs sample data to the output file.
private static final int FILE_VIDEO_ONLY_SAMPLE_DATA_LENGTH = 89_502;
private static final int FILE_AUDIO_ONLY_SAMPLE_DATA_LENGTH = 2834;
private static final int FILE_AUDIO_VIDEO_AUDIO_SAMPLE_DATA_LENGTH = 9529;
private static final int FILE_AUDIO_VIDEO_VIDEO_SAMPLE_DATA_LENGTH = 89_876;
private static final String URI_PREFIX = "asset:///media/";
private static final String FILE_VIDEO_ONLY = "mkv/sample.mkv";
private static final String FILE_AUDIO_ONLY = "amr/sample_nb.amr";
private static final String FILE_AUDIO_VIDEO = "mp4/sample.mp4";
private static final String FILE_WITH_SUBTITLES = "mkv/sample_with_srt.mkv";
private static final String FILE_WITH_SEF_SLOW_MOTION = "mp4/sample_sef_slow_motion.mp4";
private static final String FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED = "mp4/sample_ac3.mp4";
private static final String FILE_UNKNOWN_DURATION = "mp4/sample_fragmented.mp4";
public static final String DUMP_FILE_OUTPUT_DIRECTORY = "transformerdumps";
public static final String DUMP_FILE_EXTENSION = "dump";
private Context context;
private String outputPath;
private TestMuxer testMuxer;
private AutoAdvancingFakeClock clock;
private ProgressHolder progressHolder;
@ -88,55 +91,78 @@ public final class TransformerTest {
@Test
public void startTransformation_videoOnly_completesSuccessfully() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_VIDEO_ONLY_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY));
}
@Test
public void startTransformation_audioOnly_completesSuccessfully() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_ONLY);
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_AUDIO_ONLY_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_ONLY));
}
@Test
public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_VIDEO);
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length())
.isEqualTo(
FILE_AUDIO_VIDEO_VIDEO_SAMPLE_DATA_LENGTH + FILE_AUDIO_VIDEO_AUDIO_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
public void startTransformation_withSubtitles_completesSuccessfully() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mkv/sample_with_srt.mkv");
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_SUBTITLES);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(89_502);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES));
}
@Test
public void startTransformation_successiveTransformations_completesSuccessfully()
throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
// Transform first media item.
transformer.startTransformation(mediaItem, outputPath);
@ -147,13 +173,13 @@ public final class TransformerTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_VIDEO_ONLY_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY));
}
@Test
public void startTransformation_concurrentTransformations_throwsError() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
@ -164,25 +190,37 @@ public final class TransformerTest {
@Test
public void startTransformation_removeAudio_completesSuccessfully() throws Exception {
Transformer transformer =
new Transformer.Builder().setContext(context).setRemoveAudio(true).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_VIDEO);
new Transformer.Builder()
.setContext(context)
.setRemoveAudio(true)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_AUDIO_VIDEO_VIDEO_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
}
@Test
public void startTransformation_removeVideo_completesSuccessfully() throws Exception {
Transformer transformer =
new Transformer.Builder().setContext(context).setRemoveVideo(true).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_VIDEO);
new Transformer.Builder()
.setContext(context)
.setRemoveVideo(true)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_AUDIO_VIDEO_AUDIO_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
}
@Test
@ -192,13 +230,14 @@ public final class TransformerTest {
.setContext(context)
.setFlattenForSlowMotion(true)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample_sef_slow_motion.mp4");
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(18_172);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
}
@Test
@ -217,7 +256,7 @@ public final class TransformerTest {
public void startTransformation_withAllSampleFormatsUnsupported_completesWithError()
throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample_ac3.mp4");
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED);
transformer.startTransformation(mediaItem, outputPath);
Exception exception = TransformerTestRunner.runUntilError(transformer);
@ -227,8 +266,13 @@ public final class TransformerTest {
@Test
public void startTransformation_afterCancellation_completesSuccessfully() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
Transformer transformer =
new Transformer.Builder()
.setContext(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
transformer.cancel();
@ -238,7 +282,7 @@ public final class TransformerTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
assertThat(new File(outputPath).length()).isEqualTo(FILE_VIDEO_ONLY_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY));
}
@Test
@ -247,8 +291,13 @@ public final class TransformerTest {
anotherThread.start();
Looper looper = anotherThread.getLooper();
Transformer transformer =
new Transformer.Builder().setContext(context).setLooper(looper).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_ONLY);
new Transformer.Builder()
.setContext(context)
.setLooper(looper)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
AtomicReference<Exception> exception = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -267,13 +316,13 @@ public final class TransformerTest {
countDownLatch.await();
assertThat(exception.get()).isNull();
assertThat(new File(outputPath).length()).isEqualTo(FILE_AUDIO_ONLY_SAMPLE_DATA_LENGTH);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_ONLY));
}
@Test
public void startTransformation_fromWrongThread_throwsError() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_AUDIO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -300,7 +349,7 @@ public final class TransformerTest {
@Test
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
AtomicBoolean foundInconsistentState = new AtomicBoolean();
@ -346,7 +395,7 @@ public final class TransformerTest {
@Test
public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
List<Integer> progresses = new ArrayList<>();
Handler progressHandler =
new Handler(Looper.myLooper()) {
@ -381,7 +430,7 @@ public final class TransformerTest {
@Test
public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
transformer.startTransformation(mediaItem, outputPath);
@ -395,7 +444,7 @@ public final class TransformerTest {
@Test
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample_fragmented.mp4");
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_UNKNOWN_DURATION);
AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
AtomicBoolean foundInconsistentState = new AtomicBoolean();
@ -462,7 +511,7 @@ public final class TransformerTest {
@Test
public void cancel_afterCompletion_doesNotThrow() throws Exception {
Transformer transformer = new Transformer.Builder().setContext(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri(FILE_VIDEO_ONLY);
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
@ -507,4 +556,28 @@ public final class TransformerTest {
private static void removeEncodersAndDecoders() {
ShadowMediaCodec.clearCodecs();
}
private static String getDumpFileName(String originalFileName) {
return DUMP_FILE_OUTPUT_DIRECTORY + '/' + originalFileName + '.' + DUMP_FILE_EXTENSION;
}
private final class TestMuxerFactory implements Muxer.Factory {
@Override
public Muxer create(String path, String outputMimeType) throws IOException {
testMuxer = new TestMuxer(path, outputMimeType);
return testMuxer;
}
@Override
public Muxer create(ParcelFileDescriptor parcelFileDescriptor, String outputMimeType)
throws IOException {
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), outputMimeType);
return testMuxer;
}
@Override
public boolean supportsOutputMimeType(String mimeType) {
return true;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
containerMimeType = video/mp4
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.640034
width = 1080
height = 720
selectionFlags = 1
language = und
initializationData:
data = length 30, hash F6F3D010
data = length 10, hash 7A0D0F2B
sample:
trackIndex = 0
dataHashCode = -252482306
isKeyFrame = true
presentationTimeUs = 67000
sample:
trackIndex = 0
dataHashCode = 67864034
isKeyFrame = false
presentationTimeUs = 134000
sample:
trackIndex = 0
dataHashCode = 897273234
isKeyFrame = false
presentationTimeUs = 100000
sample:
trackIndex = 0
dataHashCode = -1549870586
isKeyFrame = false
presentationTimeUs = 267000
sample:
trackIndex = 0
dataHashCode = 672384813
isKeyFrame = false
presentationTimeUs = 200000
sample:
trackIndex = 0
dataHashCode = -988996493
isKeyFrame = false
presentationTimeUs = 167000
sample:
trackIndex = 0
dataHashCode = 1711151377
isKeyFrame = false
presentationTimeUs = 234000
sample:
trackIndex = 0
dataHashCode = -506806036
isKeyFrame = false
presentationTimeUs = 400000
sample:
trackIndex = 0
dataHashCode = 1902167649
isKeyFrame = false
presentationTimeUs = 334000
sample:
trackIndex = 0
dataHashCode = 2054873212
isKeyFrame = false
presentationTimeUs = 300000
sample:
trackIndex = 0
dataHashCode = 1556608231
isKeyFrame = false
presentationTimeUs = 367000
sample:
trackIndex = 0
dataHashCode = -1648978019
isKeyFrame = false
presentationTimeUs = 500000
sample:
trackIndex = 0
dataHashCode = -484808327
isKeyFrame = false
presentationTimeUs = 467000
sample:
trackIndex = 0
dataHashCode = -20706048
isKeyFrame = false
presentationTimeUs = 434000
sample:
trackIndex = 0
dataHashCode = 2085064574
isKeyFrame = false
presentationTimeUs = 634000
sample:
trackIndex = 0
dataHashCode = -637074022
isKeyFrame = false
presentationTimeUs = 567000
sample:
trackIndex = 0
dataHashCode = -1824027029
isKeyFrame = false
presentationTimeUs = 534000
sample:
trackIndex = 0
dataHashCode = -1701945306
isKeyFrame = false
presentationTimeUs = 600000
sample:
trackIndex = 0
dataHashCode = -952425536
isKeyFrame = false
presentationTimeUs = 767000
sample:
trackIndex = 0
dataHashCode = -1978031576
isKeyFrame = false
presentationTimeUs = 700000
sample:
trackIndex = 0
dataHashCode = -2128215508
isKeyFrame = false
presentationTimeUs = 667000
sample:
trackIndex = 0
dataHashCode = -259850011
isKeyFrame = false
presentationTimeUs = 734000
sample:
trackIndex = 0
dataHashCode = 1920983928
isKeyFrame = false
presentationTimeUs = 900000
sample:
trackIndex = 0
dataHashCode = 1100642337
isKeyFrame = false
presentationTimeUs = 834000
sample:
trackIndex = 0
dataHashCode = 1544917830
isKeyFrame = false
presentationTimeUs = 800000
sample:
trackIndex = 0
dataHashCode = -116205995
isKeyFrame = false
presentationTimeUs = 867000
sample:
trackIndex = 0
dataHashCode = 696343585
isKeyFrame = false
presentationTimeUs = 1034000
sample:
trackIndex = 0
dataHashCode = -644371190
isKeyFrame = false
presentationTimeUs = 967000
sample:
trackIndex = 0
dataHashCode = -1606273467
isKeyFrame = false
presentationTimeUs = 934000
sample:
trackIndex = 0
dataHashCode = -571265861
isKeyFrame = false
presentationTimeUs = 1000000
released = true

View file

@ -0,0 +1,163 @@
containerMimeType = video/mp4
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.640034
width = 1080
height = 720
selectionFlags = 1
language = und
initializationData:
data = length 30, hash F6F3D010
data = length 10, hash 7A0D0F2B
sample:
trackIndex = 0
dataHashCode = -252482306
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = 67864034
isKeyFrame = false
presentationTimeUs = 67000
sample:
trackIndex = 0
dataHashCode = 897273234
isKeyFrame = false
presentationTimeUs = 33000
sample:
trackIndex = 0
dataHashCode = -1549870586
isKeyFrame = false
presentationTimeUs = 200000
sample:
trackIndex = 0
dataHashCode = 672384813
isKeyFrame = false
presentationTimeUs = 133000
sample:
trackIndex = 0
dataHashCode = -988996493
isKeyFrame = false
presentationTimeUs = 100000
sample:
trackIndex = 0
dataHashCode = 1711151377
isKeyFrame = false
presentationTimeUs = 167000
sample:
trackIndex = 0
dataHashCode = -506806036
isKeyFrame = false
presentationTimeUs = 333000
sample:
trackIndex = 0
dataHashCode = 1902167649
isKeyFrame = false
presentationTimeUs = 267000
sample:
trackIndex = 0
dataHashCode = 2054873212
isKeyFrame = false
presentationTimeUs = 233000
sample:
trackIndex = 0
dataHashCode = 1556608231
isKeyFrame = false
presentationTimeUs = 300000
sample:
trackIndex = 0
dataHashCode = -1648978019
isKeyFrame = false
presentationTimeUs = 433000
sample:
trackIndex = 0
dataHashCode = -484808327
isKeyFrame = false
presentationTimeUs = 400000
sample:
trackIndex = 0
dataHashCode = -20706048
isKeyFrame = false
presentationTimeUs = 367000
sample:
trackIndex = 0
dataHashCode = 2085064574
isKeyFrame = false
presentationTimeUs = 567000
sample:
trackIndex = 0
dataHashCode = -637074022
isKeyFrame = false
presentationTimeUs = 500000
sample:
trackIndex = 0
dataHashCode = -1824027029
isKeyFrame = false
presentationTimeUs = 467000
sample:
trackIndex = 0
dataHashCode = -1701945306
isKeyFrame = false
presentationTimeUs = 533000
sample:
trackIndex = 0
dataHashCode = -952425536
isKeyFrame = false
presentationTimeUs = 700000
sample:
trackIndex = 0
dataHashCode = -1978031576
isKeyFrame = false
presentationTimeUs = 633000
sample:
trackIndex = 0
dataHashCode = -2128215508
isKeyFrame = false
presentationTimeUs = 600000
sample:
trackIndex = 0
dataHashCode = -259850011
isKeyFrame = false
presentationTimeUs = 667000
sample:
trackIndex = 0
dataHashCode = 1920983928
isKeyFrame = false
presentationTimeUs = 833000
sample:
trackIndex = 0
dataHashCode = 1100642337
isKeyFrame = false
presentationTimeUs = 767000
sample:
trackIndex = 0
dataHashCode = 1544917830
isKeyFrame = false
presentationTimeUs = 733000
sample:
trackIndex = 0
dataHashCode = -116205995
isKeyFrame = false
presentationTimeUs = 800000
sample:
trackIndex = 0
dataHashCode = 696343585
isKeyFrame = false
presentationTimeUs = 967000
sample:
trackIndex = 0
dataHashCode = -644371190
isKeyFrame = false
presentationTimeUs = 900000
sample:
trackIndex = 0
dataHashCode = -1606273467
isKeyFrame = false
presentationTimeUs = 867000
sample:
trackIndex = 0
dataHashCode = -571265861
isKeyFrame = false
presentationTimeUs = 933000
released = true

View file

@ -0,0 +1,392 @@
containerMimeType = video/mp4
format 0:
sampleMimeType = audio/mp4a-latm
channelCount = 1
sampleRate = 44100
format 1:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
maxInputSize = 36722
width = 1080
height = 720
frameRate = 29.970028
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample:
trackIndex = 1
dataHashCode = -770308242
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 1
dataHashCode = -732087136
isKeyFrame = false
presentationTimeUs = 66733
sample:
trackIndex = 1
dataHashCode = 468156717
isKeyFrame = false
presentationTimeUs = 33366
sample:
trackIndex = 1
dataHashCode = 1150349584
isKeyFrame = false
presentationTimeUs = 200200
sample:
trackIndex = 1
dataHashCode = 1443582006
isKeyFrame = false
presentationTimeUs = 133466
sample:
trackIndex = 1
dataHashCode = -310585145
isKeyFrame = false
presentationTimeUs = 100100
sample:
trackIndex = 1
dataHashCode = 807460688
isKeyFrame = false
presentationTimeUs = 166833
sample:
trackIndex = 1
dataHashCode = 1936487090
isKeyFrame = false
presentationTimeUs = 333666
sample:
trackIndex = 1
dataHashCode = -32297181
isKeyFrame = false
presentationTimeUs = 266933
sample:
trackIndex = 1
dataHashCode = 1529616406
isKeyFrame = false
presentationTimeUs = 233566
sample:
trackIndex = 1
dataHashCode = 1949198785
isKeyFrame = false
presentationTimeUs = 300300
sample:
trackIndex = 1
dataHashCode = -147880287
isKeyFrame = false
presentationTimeUs = 433766
sample:
trackIndex = 1
dataHashCode = 1369083472
isKeyFrame = false
presentationTimeUs = 400400
sample:
trackIndex = 1
dataHashCode = 965782073
isKeyFrame = false
presentationTimeUs = 367033
sample:
trackIndex = 1
dataHashCode = -261176150
isKeyFrame = false
presentationTimeUs = 567233
sample:
trackIndex = 0
dataHashCode = 1205768497
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = 837571078
isKeyFrame = true
presentationTimeUs = 249
sample:
trackIndex = 0
dataHashCode = -1991633045
isKeyFrame = true
presentationTimeUs = 317
sample:
trackIndex = 0
dataHashCode = -822987359
isKeyFrame = true
presentationTimeUs = 1995
sample:
trackIndex = 0
dataHashCode = -1141508176
isKeyFrame = true
presentationTimeUs = 4126
sample:
trackIndex = 0
dataHashCode = -226971245
isKeyFrame = true
presentationTimeUs = 6438
sample:
trackIndex = 0
dataHashCode = -2099636855
isKeyFrame = true
presentationTimeUs = 8818
sample:
trackIndex = 0
dataHashCode = 1541550559
isKeyFrame = true
presentationTimeUs = 11198
sample:
trackIndex = 0
dataHashCode = 411148001
isKeyFrame = true
presentationTimeUs = 13533
sample:
trackIndex = 0
dataHashCode = -897603973
isKeyFrame = true
presentationTimeUs = 16072
sample:
trackIndex = 0
dataHashCode = 1478106136
isKeyFrame = true
presentationTimeUs = 18498
sample:
trackIndex = 0
dataHashCode = -1380417145
isKeyFrame = true
presentationTimeUs = 20878
sample:
trackIndex = 0
dataHashCode = 780903644
isKeyFrame = true
presentationTimeUs = 23326
sample:
trackIndex = 0
dataHashCode = 586204432
isKeyFrame = true
presentationTimeUs = 25911
sample:
trackIndex = 0
dataHashCode = -2038771492
isKeyFrame = true
presentationTimeUs = 28541
sample:
trackIndex = 0
dataHashCode = -2065161304
isKeyFrame = true
presentationTimeUs = 31194
sample:
trackIndex = 0
dataHashCode = 468662933
isKeyFrame = true
presentationTimeUs = 33801
sample:
trackIndex = 0
dataHashCode = -358398546
isKeyFrame = true
presentationTimeUs = 36363
sample:
trackIndex = 0
dataHashCode = 1767325983
isKeyFrame = true
presentationTimeUs = 38811
sample:
trackIndex = 0
dataHashCode = 1093095458
isKeyFrame = true
presentationTimeUs = 41396
sample:
trackIndex = 0
dataHashCode = 1687543702
isKeyFrame = true
presentationTimeUs = 43867
sample:
trackIndex = 0
dataHashCode = 1675188486
isKeyFrame = true
presentationTimeUs = 46588
sample:
trackIndex = 0
dataHashCode = 888567545
isKeyFrame = true
presentationTimeUs = 49173
sample:
trackIndex = 0
dataHashCode = -439631803
isKeyFrame = true
presentationTimeUs = 51871
sample:
trackIndex = 0
dataHashCode = 1606694497
isKeyFrame = true
presentationTimeUs = 54524
sample:
trackIndex = 0
dataHashCode = 1747388653
isKeyFrame = true
presentationTimeUs = 57131
sample:
trackIndex = 0
dataHashCode = -734560004
isKeyFrame = true
presentationTimeUs = 59579
sample:
trackIndex = 0
dataHashCode = -975079040
isKeyFrame = true
presentationTimeUs = 62277
sample:
trackIndex = 0
dataHashCode = -1403504710
isKeyFrame = true
presentationTimeUs = 65020
sample:
trackIndex = 0
dataHashCode = 379512981
isKeyFrame = true
presentationTimeUs = 67627
sample:
trackIndex = 1
dataHashCode = -1830836678
isKeyFrame = false
presentationTimeUs = 500500
sample:
trackIndex = 1
dataHashCode = 1767407540
isKeyFrame = false
presentationTimeUs = 467133
sample:
trackIndex = 1
dataHashCode = 918440283
isKeyFrame = false
presentationTimeUs = 533866
sample:
trackIndex = 1
dataHashCode = -1408463661
isKeyFrame = false
presentationTimeUs = 700700
sample:
trackIndex = 0
dataHashCode = -997198863
isKeyFrame = true
presentationTimeUs = 70234
sample:
trackIndex = 0
dataHashCode = 1394492825
isKeyFrame = true
presentationTimeUs = 72932
sample:
trackIndex = 0
dataHashCode = -885232755
isKeyFrame = true
presentationTimeUs = 75471
sample:
trackIndex = 0
dataHashCode = 260871367
isKeyFrame = true
presentationTimeUs = 78101
sample:
trackIndex = 0
dataHashCode = -1505318960
isKeyFrame = true
presentationTimeUs = 80844
sample:
trackIndex = 0
dataHashCode = -390625371
isKeyFrame = true
presentationTimeUs = 83474
sample:
trackIndex = 0
dataHashCode = 1067950751
isKeyFrame = true
presentationTimeUs = 86149
sample:
trackIndex = 0
dataHashCode = -1179436278
isKeyFrame = true
presentationTimeUs = 88734
sample:
trackIndex = 0
dataHashCode = 1906607774
isKeyFrame = true
presentationTimeUs = 91387
sample:
trackIndex = 0
dataHashCode = -800475828
isKeyFrame = true
presentationTimeUs = 94380
sample:
trackIndex = 0
dataHashCode = 1718972977
isKeyFrame = true
presentationTimeUs = 97282
sample:
trackIndex = 0
dataHashCode = -1120448741
isKeyFrame = true
presentationTimeUs = 99844
sample:
trackIndex = 0
dataHashCode = -1718323210
isKeyFrame = true
presentationTimeUs = 102406
sample:
trackIndex = 0
dataHashCode = -422416
isKeyFrame = true
presentationTimeUs = 105059
sample:
trackIndex = 0
dataHashCode = 833757830
isKeyFrame = true
presentationTimeUs = 107644
sample:
trackIndex = 1
dataHashCode = 1569455924
isKeyFrame = false
presentationTimeUs = 633966
sample:
trackIndex = 1
dataHashCode = -1723778407
isKeyFrame = false
presentationTimeUs = 600600
sample:
trackIndex = 1
dataHashCode = 1578275472
isKeyFrame = false
presentationTimeUs = 667333
sample:
trackIndex = 1
dataHashCode = 1989768395
isKeyFrame = false
presentationTimeUs = 834166
sample:
trackIndex = 1
dataHashCode = -1215674502
isKeyFrame = false
presentationTimeUs = 767433
sample:
trackIndex = 1
dataHashCode = -814473606
isKeyFrame = false
presentationTimeUs = 734066
sample:
trackIndex = 1
dataHashCode = 498370894
isKeyFrame = false
presentationTimeUs = 800800
sample:
trackIndex = 1
dataHashCode = -1051506468
isKeyFrame = false
presentationTimeUs = 967633
sample:
trackIndex = 1
dataHashCode = -1025604144
isKeyFrame = false
presentationTimeUs = 900900
sample:
trackIndex = 1
dataHashCode = -913586520
isKeyFrame = false
presentationTimeUs = 867533
sample:
trackIndex = 1
dataHashCode = 1340459242
isKeyFrame = false
presentationTimeUs = 934266
released = true

View file

@ -0,0 +1,163 @@
containerMimeType = video/mp4
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
maxInputSize = 36722
width = 1080
height = 720
frameRate = 29.970028
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample:
trackIndex = 0
dataHashCode = -770308242
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = -732087136
isKeyFrame = false
presentationTimeUs = 66733
sample:
trackIndex = 0
dataHashCode = 468156717
isKeyFrame = false
presentationTimeUs = 33366
sample:
trackIndex = 0
dataHashCode = 1150349584
isKeyFrame = false
presentationTimeUs = 200200
sample:
trackIndex = 0
dataHashCode = 1443582006
isKeyFrame = false
presentationTimeUs = 133466
sample:
trackIndex = 0
dataHashCode = -310585145
isKeyFrame = false
presentationTimeUs = 100100
sample:
trackIndex = 0
dataHashCode = 807460688
isKeyFrame = false
presentationTimeUs = 166833
sample:
trackIndex = 0
dataHashCode = 1936487090
isKeyFrame = false
presentationTimeUs = 333666
sample:
trackIndex = 0
dataHashCode = -32297181
isKeyFrame = false
presentationTimeUs = 266933
sample:
trackIndex = 0
dataHashCode = 1529616406
isKeyFrame = false
presentationTimeUs = 233566
sample:
trackIndex = 0
dataHashCode = 1949198785
isKeyFrame = false
presentationTimeUs = 300300
sample:
trackIndex = 0
dataHashCode = -147880287
isKeyFrame = false
presentationTimeUs = 433766
sample:
trackIndex = 0
dataHashCode = 1369083472
isKeyFrame = false
presentationTimeUs = 400400
sample:
trackIndex = 0
dataHashCode = 965782073
isKeyFrame = false
presentationTimeUs = 367033
sample:
trackIndex = 0
dataHashCode = -261176150
isKeyFrame = false
presentationTimeUs = 567233
sample:
trackIndex = 0
dataHashCode = -1830836678
isKeyFrame = false
presentationTimeUs = 500500
sample:
trackIndex = 0
dataHashCode = 1767407540
isKeyFrame = false
presentationTimeUs = 467133
sample:
trackIndex = 0
dataHashCode = 918440283
isKeyFrame = false
presentationTimeUs = 533866
sample:
trackIndex = 0
dataHashCode = -1408463661
isKeyFrame = false
presentationTimeUs = 700700
sample:
trackIndex = 0
dataHashCode = 1569455924
isKeyFrame = false
presentationTimeUs = 633966
sample:
trackIndex = 0
dataHashCode = -1723778407
isKeyFrame = false
presentationTimeUs = 600600
sample:
trackIndex = 0
dataHashCode = 1578275472
isKeyFrame = false
presentationTimeUs = 667333
sample:
trackIndex = 0
dataHashCode = 1989768395
isKeyFrame = false
presentationTimeUs = 834166
sample:
trackIndex = 0
dataHashCode = -1215674502
isKeyFrame = false
presentationTimeUs = 767433
sample:
trackIndex = 0
dataHashCode = -814473606
isKeyFrame = false
presentationTimeUs = 734066
sample:
trackIndex = 0
dataHashCode = 498370894
isKeyFrame = false
presentationTimeUs = 800800
sample:
trackIndex = 0
dataHashCode = -1051506468
isKeyFrame = false
presentationTimeUs = 967633
sample:
trackIndex = 0
dataHashCode = -1025604144
isKeyFrame = false
presentationTimeUs = 900900
sample:
trackIndex = 0
dataHashCode = -913586520
isKeyFrame = false
presentationTimeUs = 867533
sample:
trackIndex = 0
dataHashCode = 1340459242
isKeyFrame = false
presentationTimeUs = 934266
released = true

View file

@ -0,0 +1,231 @@
containerMimeType = video/mp4
format 0:
sampleMimeType = audio/mp4a-latm
channelCount = 1
sampleRate = 44100
sample:
trackIndex = 0
dataHashCode = 1205768497
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = 837571078
isKeyFrame = true
presentationTimeUs = 249
sample:
trackIndex = 0
dataHashCode = -1991633045
isKeyFrame = true
presentationTimeUs = 317
sample:
trackIndex = 0
dataHashCode = -822987359
isKeyFrame = true
presentationTimeUs = 1995
sample:
trackIndex = 0
dataHashCode = -1141508176
isKeyFrame = true
presentationTimeUs = 4126
sample:
trackIndex = 0
dataHashCode = -226971245
isKeyFrame = true
presentationTimeUs = 6438
sample:
trackIndex = 0
dataHashCode = -2099636855
isKeyFrame = true
presentationTimeUs = 8818
sample:
trackIndex = 0
dataHashCode = 1541550559
isKeyFrame = true
presentationTimeUs = 11198
sample:
trackIndex = 0
dataHashCode = 411148001
isKeyFrame = true
presentationTimeUs = 13533
sample:
trackIndex = 0
dataHashCode = -897603973
isKeyFrame = true
presentationTimeUs = 16072
sample:
trackIndex = 0
dataHashCode = 1478106136
isKeyFrame = true
presentationTimeUs = 18498
sample:
trackIndex = 0
dataHashCode = -1380417145
isKeyFrame = true
presentationTimeUs = 20878
sample:
trackIndex = 0
dataHashCode = 780903644
isKeyFrame = true
presentationTimeUs = 23326
sample:
trackIndex = 0
dataHashCode = 586204432
isKeyFrame = true
presentationTimeUs = 25911
sample:
trackIndex = 0
dataHashCode = -2038771492
isKeyFrame = true
presentationTimeUs = 28541
sample:
trackIndex = 0
dataHashCode = -2065161304
isKeyFrame = true
presentationTimeUs = 31194
sample:
trackIndex = 0
dataHashCode = 468662933
isKeyFrame = true
presentationTimeUs = 33801
sample:
trackIndex = 0
dataHashCode = -358398546
isKeyFrame = true
presentationTimeUs = 36363
sample:
trackIndex = 0
dataHashCode = 1767325983
isKeyFrame = true
presentationTimeUs = 38811
sample:
trackIndex = 0
dataHashCode = 1093095458
isKeyFrame = true
presentationTimeUs = 41396
sample:
trackIndex = 0
dataHashCode = 1687543702
isKeyFrame = true
presentationTimeUs = 43867
sample:
trackIndex = 0
dataHashCode = 1675188486
isKeyFrame = true
presentationTimeUs = 46588
sample:
trackIndex = 0
dataHashCode = 888567545
isKeyFrame = true
presentationTimeUs = 49173
sample:
trackIndex = 0
dataHashCode = -439631803
isKeyFrame = true
presentationTimeUs = 51871
sample:
trackIndex = 0
dataHashCode = 1606694497
isKeyFrame = true
presentationTimeUs = 54524
sample:
trackIndex = 0
dataHashCode = 1747388653
isKeyFrame = true
presentationTimeUs = 57131
sample:
trackIndex = 0
dataHashCode = -734560004
isKeyFrame = true
presentationTimeUs = 59579
sample:
trackIndex = 0
dataHashCode = -975079040
isKeyFrame = true
presentationTimeUs = 62277
sample:
trackIndex = 0
dataHashCode = -1403504710
isKeyFrame = true
presentationTimeUs = 65020
sample:
trackIndex = 0
dataHashCode = 379512981
isKeyFrame = true
presentationTimeUs = 67627
sample:
trackIndex = 0
dataHashCode = -997198863
isKeyFrame = true
presentationTimeUs = 70234
sample:
trackIndex = 0
dataHashCode = 1394492825
isKeyFrame = true
presentationTimeUs = 72932
sample:
trackIndex = 0
dataHashCode = -885232755
isKeyFrame = true
presentationTimeUs = 75471
sample:
trackIndex = 0
dataHashCode = 260871367
isKeyFrame = true
presentationTimeUs = 78101
sample:
trackIndex = 0
dataHashCode = -1505318960
isKeyFrame = true
presentationTimeUs = 80844
sample:
trackIndex = 0
dataHashCode = -390625371
isKeyFrame = true
presentationTimeUs = 83474
sample:
trackIndex = 0
dataHashCode = 1067950751
isKeyFrame = true
presentationTimeUs = 86149
sample:
trackIndex = 0
dataHashCode = -1179436278
isKeyFrame = true
presentationTimeUs = 88734
sample:
trackIndex = 0
dataHashCode = 1906607774
isKeyFrame = true
presentationTimeUs = 91387
sample:
trackIndex = 0
dataHashCode = -800475828
isKeyFrame = true
presentationTimeUs = 94380
sample:
trackIndex = 0
dataHashCode = 1718972977
isKeyFrame = true
presentationTimeUs = 97282
sample:
trackIndex = 0
dataHashCode = -1120448741
isKeyFrame = true
presentationTimeUs = 99844
sample:
trackIndex = 0
dataHashCode = -1718323210
isKeyFrame = true
presentationTimeUs = 102406
sample:
trackIndex = 0
dataHashCode = -422416
isKeyFrame = true
presentationTimeUs = 105059
sample:
trackIndex = 0
dataHashCode = 833757830
isKeyFrame = true
presentationTimeUs = 107644
released = true

View file

@ -0,0 +1,188 @@
containerMimeType = video/mp4
format 0:
sampleMimeType = audio/mp4a-latm
channelCount = 2
sampleRate = 12000
format 1:
id = 2
sampleMimeType = video/avc
codecs = avc1.64000D
maxInputSize = 5476
width = 320
height = 240
frameRate = 29.523811
metadata = entries=[mdta: key=com.android.capture.fps, smta: captureFrameRate=240.0, svcTemporalLayerCount=4, SlowMotion: segments=[Segment: startTimeMs=88, endTimeMs=879, speedDivisor=2, Segment: startTimeMs=1255, endTimeMs=1970, speedDivisor=8]]
initializationData:
data = length 33, hash D3FB879D
data = length 10, hash 7A0D0F2B
sample:
trackIndex = 1
dataHashCode = -549003117
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 1
dataHashCode = 593600631
isKeyFrame = false
presentationTimeUs = 14000
sample:
trackIndex = 1
dataHashCode = -961321612
isKeyFrame = false
presentationTimeUs = 47333
sample:
trackIndex = 1
dataHashCode = -386347143
isKeyFrame = false
presentationTimeUs = 80667
sample:
trackIndex = 1
dataHashCode = -1289764147
isKeyFrame = false
presentationTimeUs = 114000
sample:
trackIndex = 1
dataHashCode = 1337088875
isKeyFrame = false
presentationTimeUs = 147333
sample:
trackIndex = 1
dataHashCode = -322406979
isKeyFrame = false
presentationTimeUs = 180667
sample:
trackIndex = 1
dataHashCode = -1688033783
isKeyFrame = false
presentationTimeUs = 228042
sample:
trackIndex = 1
dataHashCode = -700344608
isKeyFrame = false
presentationTimeUs = 244708
sample:
trackIndex = 1
dataHashCode = -1441653629
isKeyFrame = false
presentationTimeUs = 334083
sample:
trackIndex = 1
dataHashCode = 1201357091
isKeyFrame = false
presentationTimeUs = 267416
sample:
trackIndex = 1
dataHashCode = -668484307
isKeyFrame = false
presentationTimeUs = 234083
sample:
trackIndex = 1
dataHashCode = 653508165
isKeyFrame = false
presentationTimeUs = 300750
sample:
trackIndex = 1
dataHashCode = -816848987
isKeyFrame = false
presentationTimeUs = 467416
sample:
trackIndex = 1
dataHashCode = 1842436292
isKeyFrame = false
presentationTimeUs = 400750
sample:
trackIndex = 1
dataHashCode = -559603233
isKeyFrame = false
presentationTimeUs = 367416
sample:
trackIndex = 1
dataHashCode = -666437886
isKeyFrame = false
presentationTimeUs = 434083
sample:
trackIndex = 1
dataHashCode = 182521759
isKeyFrame = false
presentationTimeUs = 600750
sample:
trackIndex = 0
dataHashCode = -212376212
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = -833872563
isKeyFrame = true
presentationTimeUs = 416
sample:
trackIndex = 0
dataHashCode = -135901925
isKeyFrame = true
presentationTimeUs = 36499
sample:
trackIndex = 0
dataHashCode = -2124187794
isKeyFrame = true
presentationTimeUs = 44415
sample:
trackIndex = 0
dataHashCode = 1016665126
isKeyFrame = true
presentationTimeUs = 63081
sample:
trackIndex = 1
dataHashCode = 2139021989
isKeyFrame = false
presentationTimeUs = 534083
sample:
trackIndex = 1
dataHashCode = 2013165108
isKeyFrame = false
presentationTimeUs = 500750
sample:
trackIndex = 1
dataHashCode = 405675195
isKeyFrame = false
presentationTimeUs = 567416
sample:
trackIndex = 1
dataHashCode = -1893277090
isKeyFrame = false
presentationTimeUs = 734083
sample:
trackIndex = 1
dataHashCode = -1554795381
isKeyFrame = false
presentationTimeUs = 667416
sample:
trackIndex = 1
dataHashCode = 1197099206
isKeyFrame = false
presentationTimeUs = 634083
sample:
trackIndex = 1
dataHashCode = -674808173
isKeyFrame = false
presentationTimeUs = 700750
sample:
trackIndex = 1
dataHashCode = -775517313
isKeyFrame = false
presentationTimeUs = 867416
sample:
trackIndex = 1
dataHashCode = -2045106113
isKeyFrame = false
presentationTimeUs = 800750
sample:
trackIndex = 1
dataHashCode = 305167697
isKeyFrame = false
presentationTimeUs = 767416
sample:
trackIndex = 1
dataHashCode = 554021920
isKeyFrame = false
presentationTimeUs = 834083
released = true

View file

@ -0,0 +1,101 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.testutil;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Function;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Wraps a {@link Format} to allow dumping it. */
public final class DumpableFormat implements Dumper.Dumpable {
private final Format format;
public final int index;
private static final Format DEFAULT_FORMAT = new Format.Builder().build();
public DumpableFormat(Format format, int index) {
this.format = format;
this.index = index;
}
@Override
public void dump(Dumper dumper) {
dumper.startBlock("format " + index);
addIfNonDefault(dumper, "averageBitrate", format -> format.averageBitrate);
addIfNonDefault(dumper, "peakBitrate", format -> format.peakBitrate);
addIfNonDefault(dumper, "id", format -> format.id);
addIfNonDefault(dumper, "containerMimeType", format -> format.containerMimeType);
addIfNonDefault(dumper, "sampleMimeType", format -> format.sampleMimeType);
addIfNonDefault(dumper, "codecs", format -> format.codecs);
addIfNonDefault(dumper, "maxInputSize", format -> format.maxInputSize);
addIfNonDefault(dumper, "width", format -> format.width);
addIfNonDefault(dumper, "height", format -> format.height);
addIfNonDefault(dumper, "frameRate", format -> format.frameRate);
addIfNonDefault(dumper, "rotationDegrees", format -> format.rotationDegrees);
addIfNonDefault(dumper, "pixelWidthHeightRatio", format -> format.pixelWidthHeightRatio);
addIfNonDefault(dumper, "channelCount", format -> format.channelCount);
addIfNonDefault(dumper, "sampleRate", format -> format.sampleRate);
addIfNonDefault(dumper, "pcmEncoding", format -> format.pcmEncoding);
addIfNonDefault(dumper, "encoderDelay", format -> format.encoderDelay);
addIfNonDefault(dumper, "encoderPadding", format -> format.encoderPadding);
addIfNonDefault(dumper, "subsampleOffsetUs", format -> format.subsampleOffsetUs);
addIfNonDefault(dumper, "selectionFlags", format -> format.selectionFlags);
addIfNonDefault(dumper, "language", format -> format.language);
addIfNonDefault(dumper, "label", format -> format.label);
if (format.drmInitData != null) {
dumper.add("drmInitData", format.drmInitData.hashCode());
}
addIfNonDefault(dumper, "metadata", format -> format.metadata);
if (!format.initializationData.isEmpty()) {
dumper.startBlock("initializationData");
for (int i = 0; i < format.initializationData.size(); i++) {
dumper.add("data", format.initializationData.get(i));
}
dumper.endBlock();
}
dumper.endBlock();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DumpableFormat that = (DumpableFormat) o;
return index == that.index && format.equals(that.format);
}
@Override
public int hashCode() {
int result = format.hashCode();
result = 31 * result + index;
return result;
}
private void addIfNonDefault(
Dumper dumper, String field, Function<Format, @NullableType Object> getFieldFunction) {
@Nullable Object thisValue = getFieldFunction.apply(format);
@Nullable Object defaultValue = getFieldFunction.apply(DEFAULT_FORMAT);
if (!Util.areEqual(thisValue, defaultValue)) {
dumper.add(field, thisValue);
}
}
}

View file

@ -26,7 +26,6 @@ import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Function;
import com.google.common.primitives.Bytes;
import java.io.EOFException;
import java.io.IOException;
@ -34,7 +33,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** A fake {@link TrackOutput}. */
public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable {
@ -284,81 +282,4 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable {
}
}
private static final class DumpableFormat implements Dumper.Dumpable {
private final Format format;
public final int index;
private static final Format DEFAULT_FORMAT = new Format.Builder().build();
public DumpableFormat(Format format, int index) {
this.format = format;
this.index = index;
}
@Override
public void dump(Dumper dumper) {
dumper.startBlock("format " + index);
addIfNonDefault(dumper, "averageBitrate", format -> format.averageBitrate);
addIfNonDefault(dumper, "peakBitrate", format -> format.peakBitrate);
addIfNonDefault(dumper, "id", format -> format.id);
addIfNonDefault(dumper, "containerMimeType", format -> format.containerMimeType);
addIfNonDefault(dumper, "sampleMimeType", format -> format.sampleMimeType);
addIfNonDefault(dumper, "codecs", format -> format.codecs);
addIfNonDefault(dumper, "maxInputSize", format -> format.maxInputSize);
addIfNonDefault(dumper, "width", format -> format.width);
addIfNonDefault(dumper, "height", format -> format.height);
addIfNonDefault(dumper, "frameRate", format -> format.frameRate);
addIfNonDefault(dumper, "rotationDegrees", format -> format.rotationDegrees);
addIfNonDefault(dumper, "pixelWidthHeightRatio", format -> format.pixelWidthHeightRatio);
addIfNonDefault(dumper, "channelCount", format -> format.channelCount);
addIfNonDefault(dumper, "sampleRate", format -> format.sampleRate);
addIfNonDefault(dumper, "pcmEncoding", format -> format.pcmEncoding);
addIfNonDefault(dumper, "encoderDelay", format -> format.encoderDelay);
addIfNonDefault(dumper, "encoderPadding", format -> format.encoderPadding);
addIfNonDefault(dumper, "subsampleOffsetUs", format -> format.subsampleOffsetUs);
addIfNonDefault(dumper, "selectionFlags", format -> format.selectionFlags);
addIfNonDefault(dumper, "language", format -> format.language);
addIfNonDefault(dumper, "label", format -> format.label);
if (format.drmInitData != null) {
dumper.add("drmInitData", format.drmInitData.hashCode());
}
addIfNonDefault(dumper, "metadata", format -> format.metadata);
if (!format.initializationData.isEmpty()) {
dumper.startBlock("initializationData");
for (int i = 0; i < format.initializationData.size(); i++) {
dumper.add("data", format.initializationData.get(i));
}
dumper.endBlock();
}
dumper.endBlock();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DumpableFormat that = (DumpableFormat) o;
return index == that.index && format.equals(that.format);
}
@Override
public int hashCode() {
int result = format.hashCode();
result = 31 * result + index;
return result;
}
private void addIfNonDefault(
Dumper dumper, String field, Function<Format, @NullableType Object> getFieldFunction) {
@Nullable Object thisValue = getFieldFunction.apply(format);
@Nullable Object defaultValue = getFieldFunction.apply(DEFAULT_FORMAT);
if (!Util.areEqual(thisValue, defaultValue)) {
dumper.add(field, thisValue);
}
}
}
}