MediaMuxer CTS test
bug:7991013
Change-Id: Ibc2c20062df1e259ee529e0338aac8ddce564303
diff --git a/tests/tests/media/res/raw/video_only_176x144_3gp_h263_25fps.mp4 b/tests/tests/media/res/raw/video_only_176x144_3gp_h263_25fps.mp4
new file mode 100644
index 0000000..944a56d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_only_176x144_3gp_h263_25fps.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
new file mode 100644
index 0000000..4e928a5
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2013 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 android.media.cts;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.MediaPlayer;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.cts.media.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+public class MediaMuxerTest extends AndroidTestCase {
+ private static final String TAG = "MediaMuxerTest";
+ private static final boolean VERBOSE = false;
+ private static final int MAX_SAMPLE_SIZE = 256 * 1024;
+ private Resources mResources;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mResources = context.getResources();
+ }
+
+ /**
+ * Test: make sure the muxer handles both video and audio tracks correctly.
+ */
+ public void testVideoAudio() throws Exception {
+ int source = R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz;
+ String outputFile = "/sdcard/videoAudio.mp4";
+ cloneAndVerify(source, outputFile, 2);
+ }
+
+ /**
+ * Test: make sure the muxer handles audio track only file correctly.
+ */
+ public void testAudioOnly() throws Exception {
+ int source = R.raw.sinesweepm4a;
+ String outputFile = "/sdcard/audioOnly.mp4";
+ cloneAndVerify(source, outputFile, 1);
+ }
+
+ /**
+ * Test: make sure the muxer handles video track only file correctly.
+ */
+ public void testVideoOnly() throws Exception {
+ int source = R.raw.video_only_176x144_3gp_h263_25fps;
+ String outputFile = "/sdcard/videoOnly.mp4";
+ cloneAndVerify(source, outputFile, 1);
+ }
+
+ /**
+ * Tests: make sure the muxer handles exceptions correctly.
+ * <br> Throws exception b/c start() is not called.
+ * <br> Throws exception b/c 2 video tracks were added.
+ * <br> Throws exception b/c 2 audio tracks were added.
+ * <br> Throws exception b/c 3 tracks were added.
+ * <br> Throws exception b/c no tracks was added.
+ * <br> Throws exception b/c a wrong format.
+ */
+ public void testIllegalStateExceptions() throws IOException {
+ String outputFile = "/sdcard/muxerExceptions.mp4";
+ MediaMuxer muxer;
+
+ // Throws exception b/c start() is not called.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer.addTrack(MediaFormat.createVideoFormat("video/mp4", 480, 320));
+
+ try {
+ muxer.stop();
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // Throws exception b/c 2 video tracks were added.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer.addTrack(MediaFormat.createVideoFormat("video/mp4", 480, 320));
+
+ try {
+ muxer.addTrack(MediaFormat.createVideoFormat("video/mp4", 480, 320));
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // Throws exception b/c 2 audio tracks were added.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4", 48000, 1));
+ try {
+ muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4", 48000, 1));
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // Throws exception b/c 3 tracks were added.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer.addTrack(MediaFormat.createVideoFormat("video/mp4", 480, 320));
+ muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4", 48000, 1));
+ try {
+
+ muxer.addTrack(MediaFormat.createVideoFormat("video/mp4", 480, 320));
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // Throws exception b/c no tracks was added.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ muxer.start();
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // Throws exception b/c a wrong format.
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ try {
+ muxer.addTrack(MediaFormat.createVideoFormat("vidoe/mp4", 480, 320));
+ fail("should throw IllegalStateException.");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+ new File(outputFile).delete();
+ }
+
+ /**
+ * Using the MediaMuxer to clone a media file.
+ */
+ private void cloneMediaUsingMuxer(int srcMedia, String dstMediaPath,
+ int expectedTrackCount) throws IOException {
+ // Set up MediaExtractor to read from the source.
+ AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+ srcFd.getLength());
+
+ int trackCount = extractor.getTrackCount();
+ assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
+
+ // Set up MediaMuxer for the destination.
+ MediaMuxer muxer;
+ muxer = new MediaMuxer(dstMediaPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+ // Set up the tracks.
+ HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
+ for (int i = 0; i < trackCount; i++) {
+ extractor.selectTrack(i);
+ MediaFormat format = extractor.getTrackFormat(i);
+ int dstIndex = muxer.addTrack(format);
+ indexMap.put(i, dstIndex);
+ }
+
+ // Copy the samples from MediaExtractor to MediaMuxer.
+ boolean sawEOS = false;
+ int bufferSize = MAX_SAMPLE_SIZE;
+ int frameCount = 0;
+ int offset = 100;
+
+ ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+ BufferInfo bufferInfo = new BufferInfo();
+ muxer.start();
+ while (!sawEOS) {
+ bufferInfo.offset = offset;
+ bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+
+ if (bufferInfo.size < 0) {
+ if (VERBOSE) {
+ Log.d(TAG, "saw input EOS.");
+ }
+ sawEOS = true;
+ bufferInfo.size = 0;
+ } else {
+ bufferInfo.presentationTimeUs = extractor.getSampleTime();
+ bufferInfo.flags = extractor.getSampleFlags();
+ int trackIndex = extractor.getSampleTrackIndex();
+
+ muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
+ bufferInfo);
+ extractor.advance();
+
+ frameCount++;
+ if (VERBOSE) {
+ Log.d(TAG, "Frame (" + frameCount + ") " +
+ "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+ " Flags:" + bufferInfo.flags +
+ " TrackIndex:" + trackIndex +
+ " Size(KB) " + bufferInfo.size / 1024);
+ }
+ }
+ }
+
+ muxer.stop();
+ muxer.release();
+ srcFd.close();
+ return;
+ }
+
+ /**
+ * Clones a media file and then compares against the source file to make
+ * sure they match.
+ */
+ private void cloneAndVerify(int srcMedia, String outputMediaFile,
+ int expectedTrackCount) throws IOException {
+ try {
+ cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount);
+ verifyAttributesMatch(srcMedia, outputMediaFile);
+ // Check the sample on 1s and 0.5s.
+ verifySamplesMatch(srcMedia, outputMediaFile, 1000000);
+ verifySamplesMatch(srcMedia, outputMediaFile, 500000);
+ } finally {
+ new File(outputMediaFile).delete();
+ }
+ }
+
+ /**
+ * Compares some attributes using MediaPlayer to make sure the cloned
+ * media file matches the source file.
+ */
+ private void verifyAttributesMatch(int srcMedia, String testMediaPath) {
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(srcMedia);
+ MediaPlayer playerSrc = new MediaPlayer();
+ MediaPlayer playerTest = new MediaPlayer();
+ try {
+ playerSrc.setDataSource(testFd.getFileDescriptor(),
+ testFd.getStartOffset(), testFd.getLength());
+ playerTest.setDataSource(testMediaPath);
+
+ playerSrc.prepare();
+ playerTest.prepare();
+
+ int durationSrc = playerSrc.getDuration();
+ int durationTest = playerTest.getDuration();
+
+ int heightSrc = playerSrc.getVideoHeight();
+ int heightTest = playerTest.getVideoHeight();
+
+ int widthSrc = playerSrc.getVideoWidth();
+ int widthTest = playerTest.getVideoWidth();
+
+ if (VERBOSE) {
+ Log.d(TAG, "Source video info : width " + widthSrc + " height "
+ + heightSrc + " duration " + durationSrc);
+ Log.d(TAG, "Test video info : width " + widthTest + " height "
+ + heightTest + " duration " + durationTest);
+ }
+
+ assertEquals("Different duration", durationSrc, durationTest);
+ assertEquals("Different height", heightSrc, heightTest);
+ assertEquals("Different width", widthSrc, widthTest);
+
+ playerSrc.stop();
+ playerTest.stop();
+
+ playerSrc.release();
+ playerTest.release();
+ } catch (Exception e) {
+ // Don't expect seeing any exception here.
+ assertTrue("verifyTwoVideos failed!", false);
+ }
+
+ }
+
+ /**
+ * Uses 2 MediaExtractor, seeking to the same position, reads the sample and
+ * makes sure the samples match.
+ */
+ private void verifySamplesMatch(int srcMedia, String testMediaPath,
+ int seekToUs) {
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(srcMedia);
+ MediaExtractor extractorSrc = new MediaExtractor();
+ extractorSrc.setDataSource(testFd.getFileDescriptor(),
+ testFd.getStartOffset(), testFd.getLength());
+ int trackCount = extractorSrc.getTrackCount();
+
+ MediaExtractor extractorTest = new MediaExtractor();
+ extractorTest.setDataSource(testMediaPath);
+
+ assertEquals("wrong number of tracks", trackCount,
+ extractorTest.getTrackCount());
+
+ // Make sure the format is the same and select them
+ for (int i = 0; i < trackCount; i++) {
+ MediaFormat formatSrc = extractorSrc.getTrackFormat(i);
+ MediaFormat formatTest = extractorTest.getTrackFormat(i);
+
+ String mimeIn = formatSrc.getString(MediaFormat.KEY_MIME);
+ String mimeOut = formatTest.getString(MediaFormat.KEY_MIME);
+ if (!(mimeIn.equals(mimeOut))) {
+ fail("format didn't match on track No." + i +
+ formatSrc.toString() + "\n" + formatTest.toString());
+ }
+ extractorSrc.selectTrack(i);
+ extractorTest.selectTrack(i);
+ }
+ // Pick a time and try to compare the frame.
+ extractorSrc.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ extractorTest.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+
+ int bufferSize = MAX_SAMPLE_SIZE;
+ ByteBuffer byteBufSrc = ByteBuffer.allocate(bufferSize);
+ ByteBuffer byteBufTest = ByteBuffer.allocate(bufferSize);
+
+ extractorSrc.readSampleData(byteBufSrc, 0);
+ extractorTest.readSampleData(byteBufTest, 0);
+
+ if (!(byteBufSrc.equals(byteBufTest))) {
+ fail("byteBuffer didn't match");
+ }
+ }
+}
+