1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import android.content.Context; 20 import android.content.res.AssetFileDescriptor; 21 import android.content.res.Resources; 22 import android.media.MediaCodec.BufferInfo; 23 import android.media.MediaExtractor; 24 import android.media.MediaFormat; 25 import android.media.MediaMetadataRetriever; 26 import android.media.MediaMuxer; 27 import android.test.AndroidTestCase; 28 import android.util.Log; 29 30 import android.media.cts.R; 31 32 import java.io.File; 33 import java.io.IOException; 34 import java.nio.ByteBuffer; 35 import java.util.HashMap; 36 37 public class MediaMuxerTest extends AndroidTestCase { 38 private static final String TAG = "MediaMuxerTest"; 39 private static final boolean VERBOSE = false; 40 private static final int MAX_SAMPLE_SIZE = 256 * 1024; 41 private static final float LATITUDE = 0.0000f; 42 private static final float LONGITUDE = -180.0f; 43 private static final float BAD_LATITUDE = 91.0f; 44 private static final float BAD_LONGITUDE = -181.0f; 45 private static final float TOLERANCE = 0.0002f; 46 private Resources mResources; 47 48 @Override setContext(Context context)49 public void setContext(Context context) { 50 super.setContext(context); 51 mResources = context.getResources(); 52 } 53 54 /** 55 * Test: make sure the muxer handles both video and audio tracks correctly. 56 */ testVideoAudio()57 public void testVideoAudio() throws Exception { 58 int source = R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz; 59 String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4") 60 .getAbsolutePath(); 61 cloneAndVerify(source, outputFile, 2, 90); 62 } 63 64 /** 65 * Test: make sure the muxer handles audio track only file correctly. 66 */ testAudioOnly()67 public void testAudioOnly() throws Exception { 68 int source = R.raw.sinesweepm4a; 69 String outputFile = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4") 70 .getAbsolutePath(); 71 cloneAndVerify(source, outputFile, 1, -1); 72 } 73 74 /** 75 * Test: make sure the muxer handles video track only file correctly. 76 */ testVideoOnly()77 public void testVideoOnly() throws Exception { 78 int source = R.raw.video_only_176x144_3gp_h263_25fps; 79 String outputFile = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4") 80 .getAbsolutePath(); 81 cloneAndVerify(source, outputFile, 1, 180); 82 } 83 84 /** 85 * Tests: make sure the muxer handles exceptions correctly. 86 * <br> Throws exception b/c start() is not called. 87 * <br> Throws exception b/c 2 video tracks were added. 88 * <br> Throws exception b/c 2 audio tracks were added. 89 * <br> Throws exception b/c 3 tracks were added. 90 * <br> Throws exception b/c no tracks was added. 91 * <br> Throws exception b/c a wrong format. 92 */ testIllegalStateExceptions()93 public void testIllegalStateExceptions() throws IOException { 94 String outputFile = File.createTempFile("MediaMuxerTest_testISEs", ".mp4") 95 .getAbsolutePath(); 96 MediaMuxer muxer; 97 98 // Throws exception b/c start() is not called. 99 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 100 muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320)); 101 102 try { 103 muxer.stop(); 104 fail("should throw IllegalStateException."); 105 } catch (IllegalStateException e) { 106 // expected 107 } finally { 108 muxer.release(); 109 } 110 111 // Throws exception b/c 2 video tracks were added. 112 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 113 muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320)); 114 115 try { 116 muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320)); 117 fail("should throw IllegalStateException."); 118 } catch (IllegalStateException e) { 119 // expected 120 } finally { 121 muxer.release(); 122 } 123 124 // Throws exception b/c 2 audio tracks were added. 125 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 126 muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1)); 127 try { 128 muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1)); 129 fail("should throw IllegalStateException."); 130 } catch (IllegalStateException e) { 131 // expected 132 } finally { 133 muxer.release(); 134 } 135 136 // Throws exception b/c 3 tracks were added. 137 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 138 muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320)); 139 muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1)); 140 try { 141 142 muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320)); 143 fail("should throw IllegalStateException."); 144 } catch (IllegalStateException e) { 145 // expected 146 } finally { 147 muxer.release(); 148 } 149 150 // Throws exception b/c no tracks was added. 151 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 152 try { 153 muxer.start(); 154 fail("should throw IllegalStateException."); 155 } catch (IllegalStateException e) { 156 // expected 157 } finally { 158 muxer.release(); 159 } 160 161 // Throws exception b/c a wrong format. 162 muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 163 try { 164 muxer.addTrack(MediaFormat.createVideoFormat("vidoe/mp4", 480, 320)); 165 fail("should throw IllegalStateException."); 166 } catch (IllegalStateException e) { 167 // expected 168 } finally { 169 muxer.release(); 170 } 171 new File(outputFile).delete(); 172 } 173 174 /** 175 * Using the MediaMuxer to clone a media file. 176 */ cloneMediaUsingMuxer(int srcMedia, String dstMediaPath, int expectedTrackCount, int degrees)177 private void cloneMediaUsingMuxer(int srcMedia, String dstMediaPath, 178 int expectedTrackCount, int degrees) throws IOException { 179 // Set up MediaExtractor to read from the source. 180 AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia); 181 MediaExtractor extractor = new MediaExtractor(); 182 extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(), 183 srcFd.getLength()); 184 185 int trackCount = extractor.getTrackCount(); 186 assertEquals("wrong number of tracks", expectedTrackCount, trackCount); 187 188 // Set up MediaMuxer for the destination. 189 MediaMuxer muxer; 190 muxer = new MediaMuxer(dstMediaPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 191 192 // Set up the tracks. 193 HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount); 194 for (int i = 0; i < trackCount; i++) { 195 extractor.selectTrack(i); 196 MediaFormat format = extractor.getTrackFormat(i); 197 int dstIndex = muxer.addTrack(format); 198 indexMap.put(i, dstIndex); 199 } 200 201 // Copy the samples from MediaExtractor to MediaMuxer. 202 boolean sawEOS = false; 203 int bufferSize = MAX_SAMPLE_SIZE; 204 int frameCount = 0; 205 int offset = 100; 206 207 ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize); 208 BufferInfo bufferInfo = new BufferInfo(); 209 210 if (degrees >= 0) { 211 muxer.setOrientationHint(degrees); 212 } 213 214 // Test setLocation out of bound cases 215 try { 216 muxer.setLocation(BAD_LATITUDE, LONGITUDE); 217 fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE 218 + "]"); 219 } catch (IllegalArgumentException e) { 220 // Expected 221 } 222 try { 223 muxer.setLocation(LATITUDE, BAD_LONGITUDE); 224 fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE 225 + "]"); 226 } catch (IllegalArgumentException e) { 227 // Expected 228 } 229 230 muxer.setLocation(LATITUDE, LONGITUDE); 231 232 muxer.start(); 233 while (!sawEOS) { 234 bufferInfo.offset = offset; 235 bufferInfo.size = extractor.readSampleData(dstBuf, offset); 236 237 if (bufferInfo.size < 0) { 238 if (VERBOSE) { 239 Log.d(TAG, "saw input EOS."); 240 } 241 sawEOS = true; 242 bufferInfo.size = 0; 243 } else { 244 bufferInfo.presentationTimeUs = extractor.getSampleTime(); 245 bufferInfo.flags = extractor.getSampleFlags(); 246 int trackIndex = extractor.getSampleTrackIndex(); 247 248 muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, 249 bufferInfo); 250 extractor.advance(); 251 252 frameCount++; 253 if (VERBOSE) { 254 Log.d(TAG, "Frame (" + frameCount + ") " + 255 "PresentationTimeUs:" + bufferInfo.presentationTimeUs + 256 " Flags:" + bufferInfo.flags + 257 " TrackIndex:" + trackIndex + 258 " Size(KB) " + bufferInfo.size / 1024); 259 } 260 } 261 } 262 263 muxer.stop(); 264 muxer.release(); 265 srcFd.close(); 266 return; 267 } 268 269 /** 270 * Clones a media file and then compares against the source file to make 271 * sure they match. 272 */ cloneAndVerify(int srcMedia, String outputMediaFile, int expectedTrackCount, int degrees)273 private void cloneAndVerify(int srcMedia, String outputMediaFile, 274 int expectedTrackCount, int degrees) throws IOException { 275 try { 276 cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount, degrees); 277 verifyAttributesMatch(srcMedia, outputMediaFile, degrees); 278 verifyLocationInFile(outputMediaFile); 279 // Check the sample on 1s and 0.5s. 280 verifySamplesMatch(srcMedia, outputMediaFile, 1000000); 281 verifySamplesMatch(srcMedia, outputMediaFile, 500000); 282 } finally { 283 new File(outputMediaFile).delete(); 284 } 285 } 286 287 /** 288 * Compares some attributes using MediaMetadataRetriever to make sure the 289 * cloned media file matches the source file. 290 */ verifyAttributesMatch(int srcMedia, String testMediaPath, int degrees)291 private void verifyAttributesMatch(int srcMedia, String testMediaPath, 292 int degrees) { 293 AssetFileDescriptor testFd = mResources.openRawResourceFd(srcMedia); 294 295 MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever(); 296 retrieverSrc.setDataSource(testFd.getFileDescriptor(), 297 testFd.getStartOffset(), testFd.getLength()); 298 299 MediaMetadataRetriever retrieverTest = new MediaMetadataRetriever(); 300 retrieverTest.setDataSource(testMediaPath); 301 302 String testDegrees = retrieverTest.extractMetadata( 303 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); 304 if (testDegrees != null) { 305 assertEquals("Different degrees", degrees, 306 Integer.parseInt(testDegrees)); 307 } 308 309 String heightSrc = retrieverSrc.extractMetadata( 310 MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); 311 String heightTest = retrieverTest.extractMetadata( 312 MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); 313 assertEquals("Different height", heightSrc, 314 heightTest); 315 316 String widthSrc = retrieverSrc.extractMetadata( 317 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); 318 String widthTest = retrieverTest.extractMetadata( 319 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); 320 assertEquals("Different height", widthSrc, 321 widthTest); 322 323 String durationSrc = retrieverSrc.extractMetadata( 324 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); 325 String durationTest = retrieverTest.extractMetadata( 326 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); 327 assertEquals("Different height", durationSrc, 328 durationTest); 329 330 retrieverSrc.release(); 331 retrieverTest.release(); 332 } 333 334 /** 335 * Uses 2 MediaExtractor, seeking to the same position, reads the sample and 336 * makes sure the samples match. 337 */ verifySamplesMatch(int srcMedia, String testMediaPath, int seekToUs)338 private void verifySamplesMatch(int srcMedia, String testMediaPath, 339 int seekToUs) throws IOException { 340 AssetFileDescriptor testFd = mResources.openRawResourceFd(srcMedia); 341 MediaExtractor extractorSrc = new MediaExtractor(); 342 extractorSrc.setDataSource(testFd.getFileDescriptor(), 343 testFd.getStartOffset(), testFd.getLength()); 344 int trackCount = extractorSrc.getTrackCount(); 345 346 MediaExtractor extractorTest = new MediaExtractor(); 347 extractorTest.setDataSource(testMediaPath); 348 349 assertEquals("wrong number of tracks", trackCount, 350 extractorTest.getTrackCount()); 351 352 // Make sure the format is the same and select them 353 for (int i = 0; i < trackCount; i++) { 354 MediaFormat formatSrc = extractorSrc.getTrackFormat(i); 355 MediaFormat formatTest = extractorTest.getTrackFormat(i); 356 357 String mimeIn = formatSrc.getString(MediaFormat.KEY_MIME); 358 String mimeOut = formatTest.getString(MediaFormat.KEY_MIME); 359 if (!(mimeIn.equals(mimeOut))) { 360 fail("format didn't match on track No." + i + 361 formatSrc.toString() + "\n" + formatTest.toString()); 362 } 363 extractorSrc.selectTrack(i); 364 extractorTest.selectTrack(i); 365 } 366 // Pick a time and try to compare the frame. 367 extractorSrc.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC); 368 extractorTest.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC); 369 370 int bufferSize = MAX_SAMPLE_SIZE; 371 ByteBuffer byteBufSrc = ByteBuffer.allocate(bufferSize); 372 ByteBuffer byteBufTest = ByteBuffer.allocate(bufferSize); 373 374 extractorSrc.readSampleData(byteBufSrc, 0); 375 extractorTest.readSampleData(byteBufTest, 0); 376 377 if (!(byteBufSrc.equals(byteBufTest))) { 378 fail("byteBuffer didn't match"); 379 } 380 } 381 verifyLocationInFile(String fileName)382 private void verifyLocationInFile(String fileName) { 383 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 384 retriever.setDataSource(fileName); 385 String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 386 assertNotNull("No location information found in file " + fileName, location); 387 388 389 // parsing String location and recover the location information in floats 390 // Make sure the tolerance is very small - due to rounding errors. 391 392 // Get the position of the -/+ sign in location String, which indicates 393 // the beginning of the longitude. 394 int minusIndex = location.lastIndexOf('-'); 395 int plusIndex = location.lastIndexOf('+'); 396 397 assertTrue("+ or - is not found or found only at the beginning [" + location + "]", 398 (minusIndex > 0 || plusIndex > 0)); 399 int index = Math.max(minusIndex, plusIndex); 400 401 float latitude = Float.parseFloat(location.substring(0, index - 1)); 402 float longitude = Float.parseFloat(location.substring(index)); 403 assertTrue("Incorrect latitude: " + latitude + " [" + location + "]", 404 Math.abs(latitude - LATITUDE) <= TOLERANCE); 405 assertTrue("Incorrect longitude: " + longitude + " [" + location + "]", 406 Math.abs(longitude - LONGITUDE) <= TOLERANCE); 407 retriever.release(); 408 } 409 } 410 411