1 /* 2 * Copyright (C) 2019 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.mediav2.cts; 18 19 import static android.mediav2.common.cts.CodecEncoderTestBase.isMediaTypeContainerPairValid; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertNotNull; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 26 import android.media.MediaCodec; 27 import android.media.MediaExtractor; 28 import android.media.MediaFormat; 29 import android.media.MediaMetadataRetriever; 30 import android.media.MediaMuxer; 31 import android.os.Build; 32 import android.util.Log; 33 34 import androidx.test.filters.LargeTest; 35 import androidx.test.filters.SmallTest; 36 import androidx.test.platform.app.InstrumentationRegistry; 37 38 import com.android.compatibility.common.util.ApiTest; 39 import com.android.compatibility.common.util.CddTest; 40 import com.android.compatibility.common.util.FrameworkSpecificTest; 41 import com.android.compatibility.common.util.NonMainlineTest; 42 43 import org.junit.After; 44 import org.junit.Assume; 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.junit.experimental.runners.Enclosed; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.Parameterized; 50 51 import java.io.File; 52 import java.io.IOException; 53 import java.io.RandomAccessFile; 54 import java.nio.ByteBuffer; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Collection; 58 import java.util.HashMap; 59 import java.util.List; 60 61 /** 62 * MuxerTestHelper breaks a media file to elements that a muxer can use to rebuild its clone. 63 * While testing muxer, if the test doesn't use MediaCodecs class to generate elementary 64 * stream, but uses MediaExtractor, this class will be handy 65 */ 66 class MuxerTestHelper { 67 private static final String LOG_TAG = MuxerTestHelper.class.getSimpleName(); 68 private static final boolean ENABLE_LOGS = false; 69 // Stts values within 0.1ms(100us) difference are fudged to save too 70 // many stts entries in MPEG4Writer. 71 static final int STTS_TOLERANCE_US = 100; 72 private String mSrcPath; 73 private String mMediaType; 74 private int mTrackCount; 75 private ArrayList<MediaFormat> mFormat = new ArrayList<>(); 76 private ByteBuffer mBuff; 77 private ArrayList<ArrayList<MediaCodec.BufferInfo>> mBufferInfo; 78 private HashMap<Integer, Integer> mInpIndexMap = new HashMap<>(); 79 private ArrayList<Integer> mTrackIdxOrder = new ArrayList<>(); 80 private int mFrameLimit; 81 // combineMedias() uses local version of this variable 82 private HashMap<Integer, Integer> mOutIndexMap = new HashMap<>(); 83 private boolean mRemoveCSD; 84 splitMediaToMuxerParameters()85 private void splitMediaToMuxerParameters() throws IOException { 86 // Set up MediaExtractor to read from the source. 87 MediaExtractor extractor = new MediaExtractor(); 88 extractor.setDataSource(mSrcPath); 89 90 // Set up MediaFormat 91 int index = 0; 92 for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) { 93 extractor.selectTrack(trackID); 94 MediaFormat format = extractor.getTrackFormat(trackID); 95 if (mRemoveCSD) { 96 for (int i = 0; ; ++i) { 97 String csdKey = "csd-" + i; 98 if (format.containsKey(csdKey)) { 99 format.removeKey(csdKey); 100 } else { 101 break; 102 } 103 } 104 } 105 if (mMediaType == null) { 106 mTrackCount++; 107 mFormat.add(format); 108 mInpIndexMap.put(trackID, index++); 109 } else { 110 String mediaType = format.getString(MediaFormat.KEY_MIME); 111 if (mediaType != null && mediaType.equals(mMediaType)) { 112 mTrackCount++; 113 mFormat.add(format); 114 mInpIndexMap.put(trackID, index); 115 break; 116 } else { 117 extractor.unselectTrack(trackID); 118 } 119 } 120 } 121 122 if (0 == mTrackCount) { 123 extractor.release(); 124 throw new IllegalArgumentException("could not find usable track in file " + mSrcPath); 125 } 126 127 // Set up location for elementary stream 128 File file = new File(mSrcPath); 129 int bufferSize = (int) file.length(); 130 bufferSize = ((bufferSize + 127) >> 7) << 7; 131 // Ideally, Sum of return values of extractor.readSampleData(...) should not exceed 132 // source file size. But in case of Vorbis, aosp extractor appends an additional 4 bytes to 133 // the data at every readSampleData() call. bufferSize <<= 1 empirically large enough to 134 // hold the excess 4 bytes per read call 135 bufferSize <<= 1; 136 mBuff = ByteBuffer.allocate(bufferSize); 137 138 // Set up space for bufferInfo of all samples of all tracks 139 mBufferInfo = new ArrayList<>(mTrackCount); 140 for (index = 0; index < mTrackCount; index++) { 141 mBufferInfo.add(new ArrayList<MediaCodec.BufferInfo>()); 142 } 143 144 // Let MediaExtractor do its thing 145 boolean sawEOS = false; 146 int frameCount = 0; 147 int offset = 0; 148 while (!sawEOS && frameCount < mFrameLimit) { 149 int trackID; 150 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 151 bufferInfo.offset = offset; 152 bufferInfo.size = extractor.readSampleData(mBuff, offset); 153 154 if (bufferInfo.size < 0) { 155 sawEOS = true; 156 } else { 157 bufferInfo.presentationTimeUs = extractor.getSampleTime(); 158 bufferInfo.flags = extractor.getSampleFlags(); 159 trackID = extractor.getSampleTrackIndex(); 160 mTrackIdxOrder.add(trackID); 161 mBufferInfo.get(mInpIndexMap.get(trackID)).add(bufferInfo); 162 extractor.advance(); 163 frameCount++; 164 } 165 offset += bufferInfo.size; 166 } 167 extractor.release(); 168 } 169 registerTrack(MediaMuxer muxer)170 void registerTrack(MediaMuxer muxer) { 171 for (int trackID = 0; trackID < mTrackCount; trackID++) { 172 int dstIndex = muxer.addTrack(mFormat.get(trackID)); 173 mOutIndexMap.put(trackID, dstIndex); 174 } 175 } 176 insertSampleData(MediaMuxer muxer)177 void insertSampleData(MediaMuxer muxer) { 178 // write all registered tracks in interleaved order 179 int[] frameCount = new int[mTrackCount]; 180 for (int i = 0; i < mTrackIdxOrder.size(); i++) { 181 int trackID = mTrackIdxOrder.get(i); 182 int index = mInpIndexMap.get(trackID); 183 MediaCodec.BufferInfo bufferInfo = mBufferInfo.get(index).get(frameCount[index]); 184 muxer.writeSampleData(mOutIndexMap.get(index), mBuff, bufferInfo); 185 frameCount[index]++; 186 if (ENABLE_LOGS) { 187 Log.v(LOG_TAG, "Track: " + index + " Timestamp: " + bufferInfo.presentationTimeUs); 188 } 189 } 190 if (ENABLE_LOGS) { 191 Log.v(LOG_TAG, "Total samples: " + mTrackIdxOrder.size()); 192 } 193 } 194 muxMedia(MediaMuxer muxer)195 void muxMedia(MediaMuxer muxer) { 196 registerTrack(muxer); 197 muxer.start(); 198 insertSampleData(muxer); 199 muxer.stop(); 200 } 201 combineMedias(MediaMuxer muxer, Object o, int[] repeater)202 void combineMedias(MediaMuxer muxer, Object o, int[] repeater) { 203 if (o == null || getClass() != o.getClass()) 204 throw new IllegalArgumentException("Invalid Object handle"); 205 if (null == repeater || repeater.length < 2) 206 throw new IllegalArgumentException("Invalid Parameter, repeater"); 207 MuxerTestHelper that = (MuxerTestHelper) o; 208 209 // add tracks 210 int totalTracksToAdd = repeater[0] * this.mTrackCount + repeater[1] * that.mTrackCount; 211 int[] outIndexMap = new int[totalTracksToAdd]; 212 MuxerTestHelper[] group = {this, that}; 213 for (int k = 0, idx = 0; k < group.length; k++) { 214 for (int j = 0; j < repeater[k]; j++) { 215 for (MediaFormat format : group[k].mFormat) { 216 outIndexMap[idx++] = muxer.addTrack(format); 217 } 218 } 219 } 220 221 // mux samples 222 // write all registered tracks in planar order viz all samples of a track A then all 223 // samples of track B, ... 224 muxer.start(); 225 for (int k = 0, idx = 0; k < group.length; k++) { 226 for (int j = 0; j < repeater[k]; j++) { 227 for (int i = 0; i < group[k].mTrackCount; i++) { 228 ArrayList<MediaCodec.BufferInfo> bufInfos = group[k].mBufferInfo.get(i); 229 for (int p = 0; p < bufInfos.size(); p++) { 230 MediaCodec.BufferInfo bufInfo = bufInfos.get(p); 231 muxer.writeSampleData(outIndexMap[idx], group[k].mBuff, bufInfo); 232 if (ENABLE_LOGS) { 233 Log.v(LOG_TAG, "Track: " + outIndexMap[idx] + " Timestamp: " + 234 bufInfo.presentationTimeUs); 235 } 236 } 237 idx++; 238 } 239 } 240 } 241 muxer.stop(); 242 } 243 MuxerTestHelper(String srcPath, String mediaType, int frameLimit, boolean aRemoveCSD)244 MuxerTestHelper(String srcPath, String mediaType, int frameLimit, boolean aRemoveCSD) 245 throws IOException { 246 mSrcPath = srcPath; 247 mMediaType = mediaType; 248 if (frameLimit < 0) frameLimit = Integer.MAX_VALUE; 249 mFrameLimit = frameLimit; 250 mRemoveCSD = aRemoveCSD; 251 splitMediaToMuxerParameters(); 252 } 253 MuxerTestHelper(String srcPath, String mediaType)254 MuxerTestHelper(String srcPath, String mediaType) throws IOException { 255 this(srcPath, mediaType, -1, false); 256 } 257 MuxerTestHelper(String srcPath, int frameLimit)258 MuxerTestHelper(String srcPath, int frameLimit) throws IOException { 259 this(srcPath, null, frameLimit, false); 260 } 261 MuxerTestHelper(String srcPath, boolean aRemoveCSD)262 MuxerTestHelper(String srcPath, boolean aRemoveCSD) throws IOException { 263 this(srcPath, null, -1, aRemoveCSD); 264 } 265 MuxerTestHelper(String srcPath)266 MuxerTestHelper(String srcPath) throws IOException { 267 this(srcPath, null, -1, false); 268 } 269 getTrackCount()270 int getTrackCount() { 271 return mTrackCount; 272 } 273 274 // offset pts of samples from index sampleOffset till the end by tsOffset for each audio and 275 // video track offsetTimeStamp(long tsAudioOffset, long tsVideoOffset, int sampleOffset)276 void offsetTimeStamp(long tsAudioOffset, long tsVideoOffset, int sampleOffset) { 277 for (int trackID = 0; trackID < mTrackCount; trackID++) { 278 long tsOffset = 0; 279 if (mFormat.get(trackID).getString(MediaFormat.KEY_MIME).startsWith("video/")) { 280 tsOffset = tsVideoOffset; 281 } else if (mFormat.get(trackID).getString(MediaFormat.KEY_MIME).startsWith("audio/")) { 282 tsOffset = tsAudioOffset; 283 } 284 for (int i = sampleOffset; i < mBufferInfo.get(trackID).size(); i++) { 285 MediaCodec.BufferInfo bufferInfo = mBufferInfo.get(trackID).get(i); 286 bufferInfo.presentationTimeUs += tsOffset; 287 } 288 } 289 } 290 291 // returns true if 'this' stream is a subset of 'o'. That is all tracks in current media 292 // stream are present in ref media stream isSubsetOf(Object o)293 boolean isSubsetOf(Object o) { 294 if (this == o) return true; 295 if (o == null || getClass() != o.getClass()) return false; 296 MuxerTestHelper that = (MuxerTestHelper) o; 297 int MAX_SAMPLE_SIZE = 4 * 1024 * 1024; 298 byte[] refBuffer = new byte[MAX_SAMPLE_SIZE]; 299 byte[] testBuffer = new byte[MAX_SAMPLE_SIZE]; 300 for (int i = 0; i < mTrackCount; i++) { 301 MediaFormat thisFormat = mFormat.get(i); 302 String thisMediaType = thisFormat.getString(MediaFormat.KEY_MIME); 303 int j = 0; 304 for (; j < that.mTrackCount; j++) { 305 MediaFormat thatFormat = that.mFormat.get(j); 306 String thatMediaType = thatFormat.getString(MediaFormat.KEY_MIME); 307 if (thisMediaType != null && thisMediaType.equals(thatMediaType)) { 308 if (!ExtractorTest.isFormatSimilar(thisFormat, thatFormat)) continue; 309 if (mBufferInfo.get(i).size() == that.mBufferInfo.get(j).size()) { 310 long tolerance = thisMediaType.startsWith("video/") ? STTS_TOLERANCE_US : 0; 311 int k = 0; 312 for (; k < mBufferInfo.get(i).size(); k++) { 313 MediaCodec.BufferInfo thisInfo = mBufferInfo.get(i).get(k); 314 MediaCodec.BufferInfo thatInfo = that.mBufferInfo.get(j).get(k); 315 if (thisInfo.flags != thatInfo.flags) { 316 break; 317 } 318 if (thisInfo.size != thatInfo.size) { 319 break; 320 } else { 321 mBuff.position(thisInfo.offset); 322 mBuff.get(refBuffer, 0, thisInfo.size); 323 that.mBuff.position(thatInfo.offset); 324 that.mBuff.get(testBuffer, 0, thatInfo.size); 325 int count = 0; 326 for (; count < thisInfo.size; count++) { 327 if (refBuffer[count] != testBuffer[count]) { 328 break; 329 } 330 } 331 if (count != thisInfo.size) break; 332 } 333 if (Math.abs( 334 thisInfo.presentationTimeUs - thatInfo.presentationTimeUs) > 335 tolerance) { 336 break; 337 } 338 } 339 // all samples are identical. successful match found. move to next track 340 if (k == mBufferInfo.get(i).size()) break; 341 } else { 342 if (ENABLE_LOGS) { 343 Log.d(LOG_TAG, "mediaType matched but sample count different." 344 + " Total Samples ref/test: " + mBufferInfo.get(i).size() + '/' 345 + that.mBufferInfo.get(j).size()); 346 } 347 } 348 } 349 } 350 mBuff.position(0); 351 that.mBuff.position(0); 352 if (j == that.mTrackCount) { 353 if (ENABLE_LOGS) { 354 Log.d(LOG_TAG, "For track: " + thisMediaType + " Couldn't find a match "); 355 } 356 return false; 357 } 358 } 359 return true; 360 } 361 } 362 363 @RunWith(Enclosed.class) 364 public class MuxerTest { 365 // duplicate definitions of hide fields of MediaMuxer.OutputFormat. 366 private static final int MUXER_OUTPUT_FIRST = 0; 367 private static final int MUXER_OUTPUT_LAST = MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG; 368 369 private static final String MUX_SEL_KEY = "mux-sel"; 370 private static String selector; 371 private static boolean[] muxSelector = new boolean[MUXER_OUTPUT_LAST + 1]; 372 private static HashMap<Integer, String> formatStringPair = new HashMap<>(); 373 374 static { 375 android.os.Bundle args = InstrumentationRegistry.getArguments(); 376 final String defSel = "mp4;webm;3gp;ogg"; 377 selector = (null == args.getString(MUX_SEL_KEY)) ? defSel : args.getString(MUX_SEL_KEY); 378 createFormatStringPair()379 createFormatStringPair(); 380 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; format++) { 381 muxSelector[format] = selector.contains(formatStringPair.get(format)); 382 } 383 } 384 createFormatStringPair()385 static private void createFormatStringPair() { 386 formatStringPair.put(MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "mp4"); 387 formatStringPair.put(MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "webm"); 388 formatStringPair.put(MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "3gp"); 389 formatStringPair.put(MediaMuxer.OutputFormat.MUXER_OUTPUT_HEIF, "heif"); 390 formatStringPair.put(MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG, "ogg"); 391 } 392 shouldRunTest(int format)393 static private boolean shouldRunTest(int format) { 394 return muxSelector[format]; 395 } 396 397 /** 398 * Tests MediaMuxer API that are dependent on MediaMuxer.OutputFormat. setLocation, 399 * setOrientationHint are dependent on the media type and OutputFormat. Legality of these APIs 400 * are tested in this class. 401 */ 402 @FrameworkSpecificTest 403 @NonMainlineTest 404 @SmallTest 405 @RunWith(Parameterized.class) 406 public static class TestApi { 407 private int mOutFormat; 408 private String mSrcFile; 409 private String mInpPath; 410 private String mOutPath; 411 private int mTrackCount; 412 private static final float ANNAPURNA_LAT = 28.59f; 413 private static final float ANNAPURNA_LONG = 83.82f; 414 private static final float TOLERANCE = 0.0002f; 415 private static final int CURRENT_ROTATION = 180; 416 417 static { 418 System.loadLibrary("ctsmediav2muxer_jni"); 419 } 420 421 @Before prologue()422 public void prologue() throws IOException { 423 mInpPath = WorkDir.getMediaDirString() + mSrcFile; 424 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 425 } 426 427 @After epilogue()428 public void epilogue() { 429 new File(mOutPath).delete(); 430 } 431 432 @Parameterized.Parameters(name = "{index}_{3}") input()433 public static Collection<Object[]> input() { 434 return Arrays.asList(new Object[][]{ 435 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_cif_768kbps_30fps_avc.mp4", 436 1, "mp4"}, 437 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_cif_768kbps_30fps_vp9.mkv", 438 1, "webm"}, 439 {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "bbb_cif_768kbps_30fps_h263.mp4", 440 1, "3gpp"}, 441 {MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG, "bbb_stereo_48kHz_192kbps_opus.ogg", 442 1, "ogg"}, 443 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 444 "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", 2, "mp4"}, 445 }); 446 } 447 TestApi(int outFormat, String srcFile, int trackCount, String testName)448 public TestApi(int outFormat, String srcFile, int trackCount, String testName) { 449 mOutFormat = outFormat; 450 mSrcFile = srcFile; 451 mTrackCount = trackCount; 452 } 453 nativeTestSetLocation(int format, String srcPath, String outPath)454 private native boolean nativeTestSetLocation(int format, String srcPath, String outPath); 455 nativeTestSetOrientationHint(int format, String srcPath, String outPath)456 private native boolean nativeTestSetOrientationHint(int format, String srcPath, 457 String outPath); 458 nativeTestGetTrackCount(String srcPath, String outPath, int outFormat, int trackCount)459 private native boolean nativeTestGetTrackCount(String srcPath, String outPath, 460 int outFormat, int trackCount); 461 nativeTestGetTrackFormat(String srcPath, String outPath, int outFormat)462 private native boolean nativeTestGetTrackFormat(String srcPath, String outPath, 463 int outFormat); 464 verifyLocationInFile(String fileName)465 private void verifyLocationInFile(String fileName) throws IOException { 466 if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 && 467 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return; 468 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 469 retriever.setDataSource(fileName); 470 471 // parsing String location and recover the location information in floats 472 // Make sure the tolerance is very small - due to rounding errors. 473 474 // Get the position of the -/+ sign in location String, which indicates 475 // the beginning of the longitude. 476 String loc = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 477 assertTrue(loc != null); 478 int minusIndex = loc.lastIndexOf('-'); 479 int plusIndex = loc.lastIndexOf('+'); 480 481 assertTrue("+ or - is not found or found only at the beginning [" + loc + "]", 482 (minusIndex > 0 || plusIndex > 0)); 483 int index = Math.max(minusIndex, plusIndex); 484 485 float latitude = Float.parseFloat(loc.substring(0, index - 1)); 486 int lastIndex = loc.lastIndexOf('/', index); 487 if (lastIndex == -1) { 488 lastIndex = loc.length(); 489 } 490 float longitude = Float.parseFloat(loc.substring(index, lastIndex - 1)); 491 assertTrue("Incorrect latitude: " + latitude + " [" + loc + "]", 492 Math.abs(latitude - ANNAPURNA_LAT) <= TOLERANCE); 493 assertTrue("Incorrect longitude: " + longitude + " [" + loc + "]", 494 Math.abs(longitude - ANNAPURNA_LONG) <= TOLERANCE); 495 retriever.release(); 496 } 497 verifyOrientation(String fileName)498 private void verifyOrientation(String fileName) throws IOException { 499 if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 && 500 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return; 501 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 502 retriever.setDataSource(fileName); 503 504 String testDegrees = retriever.extractMetadata( 505 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); 506 assertTrue(testDegrees != null); 507 assertEquals("Different degrees " + CURRENT_ROTATION + " and " + testDegrees, 508 CURRENT_ROTATION, Integer.parseInt(testDegrees)); 509 retriever.release(); 510 } 511 512 @ApiTest(apis = "android.media.MediaMuxer#setLocation") 513 @Test testSetLocation()514 public void testSetLocation() throws IOException { 515 Assume.assumeTrue(shouldRunTest(mOutFormat)); 516 MediaMuxer muxer = new MediaMuxer(mOutPath, mOutFormat); 517 try { 518 boolean isGeoDataSupported = false; 519 final float tooFarNorth = 90.5f; 520 final float tooFarWest = -180.5f; 521 final float tooFarSouth = -90.5f; 522 final float tooFarEast = 180.5f; 523 final float atlanticLat = 14.59f; 524 final float atlanticLong = 28.67f; 525 526 try { 527 muxer.setLocation(tooFarNorth, atlanticLong); 528 fail("setLocation succeeded with bad argument: [" + tooFarNorth + "," + 529 atlanticLong + "]"); 530 } catch (Exception e) { 531 // expected 532 } 533 try { 534 muxer.setLocation(tooFarSouth, atlanticLong); 535 fail("setLocation succeeded with bad argument: [" + tooFarSouth + "," + 536 atlanticLong + "]"); 537 } catch (Exception e) { 538 // expected 539 } 540 try { 541 muxer.setLocation(atlanticLat, tooFarWest); 542 fail("setLocation succeeded with bad argument: [" + atlanticLat + "," + 543 tooFarWest + "]"); 544 } catch (Exception e) { 545 // expected 546 } 547 try { 548 muxer.setLocation(atlanticLat, tooFarEast); 549 fail("setLocation succeeded with bad argument: [" + atlanticLat + "," + 550 tooFarEast + "]"); 551 } catch (Exception e) { 552 // expected 553 } 554 try { 555 muxer.setLocation(tooFarNorth, tooFarWest); 556 fail("setLocation succeeded with bad argument: [" + tooFarNorth + "," + 557 tooFarWest + "]"); 558 } catch (Exception e) { 559 // expected 560 } 561 try { 562 muxer.setLocation(atlanticLat, atlanticLong); 563 isGeoDataSupported = true; 564 } catch (Exception e) { 565 // can happen 566 } 567 568 if (isGeoDataSupported) { 569 try { 570 muxer.setLocation(ANNAPURNA_LAT, ANNAPURNA_LONG); 571 } catch (IllegalArgumentException e) { 572 fail(e.getMessage()); 573 } 574 } else { 575 assertTrue(mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 && 576 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP); 577 } 578 579 MuxerTestHelper mediaInfo = new MuxerTestHelper(mInpPath, 60); 580 mediaInfo.registerTrack(muxer); 581 muxer.start(); 582 // after start 583 try { 584 muxer.setLocation(0.0f, 0.0f); 585 fail("SetLocation succeeded after muxer.start()"); 586 } catch (IllegalStateException e) { 587 // Exception 588 } 589 mediaInfo.insertSampleData(muxer); 590 muxer.stop(); 591 // after stop 592 try { 593 muxer.setLocation(ANNAPURNA_LAT, ANNAPURNA_LONG); 594 fail("setLocation() succeeded after muxer.stop()"); 595 } catch (IllegalStateException e) { 596 // expected 597 } 598 muxer.release(); 599 // after release 600 try { 601 muxer.setLocation(ANNAPURNA_LAT, ANNAPURNA_LONG); 602 fail("setLocation() succeeded after muxer.release()"); 603 } catch (IllegalStateException e) { 604 // expected 605 } 606 verifyLocationInFile(mOutPath); 607 } finally { 608 muxer.release(); 609 } 610 } 611 612 @ApiTest(apis = "android.media.MediaMuxer#setOrientationHint") 613 @Test testSetOrientationHint()614 public void testSetOrientationHint() throws IOException { 615 Assume.assumeTrue(shouldRunTest(mOutFormat)); 616 MediaMuxer muxer = new MediaMuxer(mOutPath, mOutFormat); 617 try { 618 boolean isOrientationSupported = false; 619 final int[] badRotation = {360, 45, -90}; 620 final int oldRotation = 90; 621 622 for (int degree : badRotation) { 623 try { 624 muxer.setOrientationHint(degree); 625 fail("setOrientationHint() succeeded with bad argument :" + degree); 626 } catch (Exception e) { 627 // expected 628 } 629 } 630 try { 631 muxer.setOrientationHint(oldRotation); 632 isOrientationSupported = true; 633 } catch (Exception e) { 634 // can happen 635 } 636 if (isOrientationSupported) { 637 try { 638 muxer.setOrientationHint(CURRENT_ROTATION); 639 } catch (IllegalArgumentException e) { 640 fail(e.getMessage()); 641 } 642 } else { 643 assertTrue(mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 && 644 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP); 645 } 646 647 MuxerTestHelper mediaInfo = new MuxerTestHelper(mInpPath, 60); 648 mediaInfo.registerTrack(muxer); 649 muxer.start(); 650 // after start 651 try { 652 muxer.setOrientationHint(0); 653 fail("setOrientationHint succeeded after muxer.start()"); 654 } catch (IllegalStateException e) { 655 // Exception 656 } 657 mediaInfo.insertSampleData(muxer); 658 muxer.stop(); 659 // after stop 660 try { 661 muxer.setOrientationHint(CURRENT_ROTATION); 662 fail("setOrientationHint() succeeded after muxer.stop()"); 663 } catch (IllegalStateException e) { 664 // expected 665 } 666 muxer.release(); 667 // after release 668 try { 669 muxer.setOrientationHint(CURRENT_ROTATION); 670 fail("setOrientationHint() succeeded after muxer.release()"); 671 } catch (IllegalStateException e) { 672 // expected 673 } 674 verifyOrientation(mOutPath); 675 } finally { 676 muxer.release(); 677 } 678 } 679 680 @ApiTest(apis = "AMediaMuxer_setLocation") 681 @Test testSetLocationNative()682 public void testSetLocationNative() throws IOException { 683 Assume.assumeTrue(shouldRunTest(mOutFormat)); 684 assertTrue(nativeTestSetLocation(mOutFormat, mInpPath, mOutPath)); 685 verifyLocationInFile(mOutPath); 686 } 687 688 @ApiTest(apis = "AMediaMuxer_setOrientationHint") 689 @Test testSetOrientationHintNative()690 public void testSetOrientationHintNative() throws IOException { 691 Assume.assumeTrue(shouldRunTest(mOutFormat)); 692 assertTrue(nativeTestSetOrientationHint(mOutFormat, mInpPath, mOutPath)); 693 verifyOrientation(mOutPath); 694 } 695 696 @ApiTest(apis = "AMediaMuxer_getTrackCount") 697 @Test testGetTrackCountNative()698 public void testGetTrackCountNative() { 699 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 700 assertTrue(nativeTestGetTrackCount(mInpPath, mOutPath, mOutFormat, mTrackCount)); 701 } 702 703 @ApiTest(apis = "AMediaMuxer_getTrackFormat") 704 @Test testGetTrackFormatNative()705 public void testGetTrackFormatNative() { 706 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 707 assertTrue(nativeTestGetTrackFormat(mInpPath, mOutPath, mOutFormat)); 708 } 709 } 710 711 /** 712 * Tests muxing multiple Video/Audio Tracks 713 */ 714 @FrameworkSpecificTest 715 @NonMainlineTest 716 @LargeTest 717 @RunWith(Parameterized.class) 718 public static class TestMultiTrack { 719 private int mOutFormat; 720 private String mSrcFileA; 721 private String mSrcFileB; 722 private String mInpPathA; 723 private String mInpPathB; 724 private String mRefPath; 725 private String mOutPath; 726 727 static { 728 System.loadLibrary("ctsmediav2muxer_jni"); 729 } 730 731 @Before prologue()732 public void prologue() throws IOException { 733 mInpPathA = WorkDir.getMediaDirString() + mSrcFileA; 734 mInpPathB = WorkDir.getMediaDirString() + mSrcFileB; 735 mRefPath = File.createTempFile("ref", ".out").getAbsolutePath(); 736 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 737 } 738 739 @After epilogue()740 public void epilogue() { 741 new File(mRefPath).delete(); 742 new File(mOutPath).delete(); 743 } 744 745 @Parameterized.Parameters(name = "{index}_{3}") input()746 public static Collection<Object[]> input() { 747 return Arrays.asList(new Object[][]{ 748 // audio, video are 3 sec 749 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_cif_768kbps_30fps_h263" + 750 ".mp4", "bbb_stereo_48kHz_192kbps_aac.mp4", "mp4"}, 751 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_cif_768kbps_30fps_vp9.mkv" 752 , "bbb_stereo_48kHz_192kbps_vorbis.ogg", "webm"}, 753 {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "bbb_cif_768kbps_30fps_h263.mp4" 754 , "bbb_mono_16kHz_20kbps_amrwb.amr", "3gpp"}, 755 756 // audio 3 sec, video 10 sec 757 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_qcif_512kbps_30fps_avc" + 758 ".mp4", "bbb_stereo_48kHz_192kbps_aac.mp4", "mp4"}, 759 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_qcif_512kbps_30fps_vp9.webm" 760 , "bbb_stereo_48kHz_192kbps_vorbis.ogg", "webm"}, 761 {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "bbb_qcif_512kbps_30fps_h263.3gp" 762 , "bbb_mono_16kHz_20kbps_amrwb.amr", "3gpp"}, 763 764 // audio 10 sec, video 3 sec 765 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_cif_768kbps_30fps_h263" + 766 ".mp4", "bbb_stereo_48kHz_128kbps_aac.mp4", "mp4"}, 767 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_cif_768kbps_30fps_vp9.mkv" 768 , "bbb_stereo_48kHz_128kbps_vorbis.ogg", "webm"}, 769 {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "bbb_cif_768kbps_30fps_h263.mp4" 770 , "bbb_mono_8kHz_8kbps_amrnb.3gp", "3gpp"}, 771 }); 772 } 773 TestMultiTrack(int outFormat, String srcFileA, String srcFileB, String testName)774 public TestMultiTrack(int outFormat, String srcFileA, String srcFileB, String testName) { 775 mOutFormat = outFormat; 776 mSrcFileA = srcFileA; 777 mSrcFileB = srcFileB; 778 } 779 nativeTestMultiTrack(int format, String fileA, String fileB, String fileR, String fileO)780 private native boolean nativeTestMultiTrack(int format, String fileA, String fileB, 781 String fileR, String fileO); 782 783 @ApiTest(apis = {"android.media.MediaMuxer#MediaMuxer", "android.media.MediaMuxer#addTrack", 784 "android.media.MediaMuxer#start", "android.media.MediaMuxer#writeSampleData", 785 "android.media.MediaMuxer#stop", "android.media.MediaMuxer#release"}) 786 @Test testMultiTrack()787 public void testMultiTrack() throws IOException { 788 Assume.assumeTrue(shouldRunTest(mOutFormat)); 789 Assume.assumeTrue("TODO(b/146423022)", 790 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM); 791 // number of times to repeat {mSrcFileA, mSrcFileB} in Output 792 // values should be in sync with nativeTestMultiTrack 793 final int[][] numTracks = {{2, 0}, {0, 2}, {1, 2}, {2, 1}, {2, 2}}; 794 795 MuxerTestHelper mediaInfoA = new MuxerTestHelper(mInpPathA); 796 MuxerTestHelper mediaInfoB = new MuxerTestHelper(mInpPathB); 797 assertEquals("error! unexpected track count", 1, mediaInfoA.getTrackCount()); 798 assertEquals("error! unexpected track count", 1, mediaInfoB.getTrackCount()); 799 800 // prepare reference 801 RandomAccessFile refFile = new RandomAccessFile(mRefPath, "rws"); 802 MediaMuxer muxer = new MediaMuxer(refFile.getFD(), mOutFormat); 803 MuxerTestHelper refInfo = null; 804 String msg = String.format("testMultiTrack: inputs: %s %s, fmt: %d ", mSrcFileA, 805 mSrcFileB, mOutFormat); 806 try { 807 mediaInfoA.combineMedias(muxer, mediaInfoB, new int[]{1, 1}); 808 refInfo = new MuxerTestHelper(mRefPath); 809 if (!mediaInfoA.isSubsetOf(refInfo) || !mediaInfoB.isSubsetOf(refInfo)) { 810 fail(msg + "error ! muxing src A and src B failed"); 811 } 812 } catch (Exception e) { 813 if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG) { 814 fail(msg + "error ! muxing src A and src B failed"); 815 } 816 } finally { 817 muxer.release(); 818 refFile.close(); 819 } 820 821 // test multi-track 822 for (int[] numTrack : numTracks) { 823 RandomAccessFile outFile = new RandomAccessFile(mOutPath, "rws"); 824 muxer = new MediaMuxer(outFile.getFD(), mOutFormat); 825 try { 826 mediaInfoA.combineMedias(muxer, mediaInfoB, numTrack); 827 MuxerTestHelper outInfo = new MuxerTestHelper(mOutPath); 828 if (!outInfo.isSubsetOf(refInfo)) { 829 fail(msg + " error ! muxing src A: " + numTrack[0] + " src B: " + 830 numTrack[1] + "failed"); 831 } 832 } catch (Exception e) { 833 if (mOutFormat == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) { 834 fail(msg + " error ! muxing src A: " + numTrack[0] + " src B: " + 835 numTrack[1] + "failed"); 836 } 837 } finally { 838 muxer.release(); 839 outFile.close(); 840 } 841 } 842 } 843 844 @ApiTest(apis = {"AMediaMuxer_new", "AMediaMuxer_addTrack", "AMediaMuxer_start", 845 "AMediaMuxer_writeSampleData", "AMediaMuxer_stop", "AMediaMuxer_delete"}) 846 @Test testMultiTrackNative()847 public void testMultiTrackNative() { 848 Assume.assumeTrue(shouldRunTest(mOutFormat)); 849 Assume.assumeTrue("TODO(b/146423022)", 850 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM); 851 assertTrue(nativeTestMultiTrack(mOutFormat, mInpPathA, mInpPathB, mRefPath, mOutPath)); 852 } 853 } 854 855 /** 856 * Add an offset to the presentation time of samples of a track. Mux with the added offset, 857 * validate by re-extracting the muxer output file and compare with original. 858 */ 859 @FrameworkSpecificTest 860 @NonMainlineTest 861 @LargeTest 862 @RunWith(Parameterized.class) 863 public static class TestOffsetPts { 864 private String mSrcFile; 865 private int mOutFormat; 866 private int[] mOffsetIndices; 867 private String mInpPath; 868 private String mOutPath; 869 870 static { 871 System.loadLibrary("ctsmediav2muxer_jni"); 872 } 873 874 @Before prologue()875 public void prologue() throws IOException { 876 mInpPath = WorkDir.getMediaDirString() + mSrcFile; 877 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 878 } 879 880 @After epilogue()881 public void epilogue() { 882 new File(mOutPath).delete(); 883 } 884 885 @Parameterized.Parameters(name = "{index}_{3}") input()886 public static Collection<Object[]> input() { 887 return Arrays.asList(new Object[][]{ 888 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 889 "bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_aac.mp4", 890 new int[]{0}, "mp4"}, 891 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, 892 "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm", 893 new int[]{0}, "webm"}, 894 {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, 895 "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", 896 new int[]{0}, "3gpp"}, 897 {MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG, "bbb_stereo_48kHz_192kbps_opus.ogg", 898 new int[]{10}, "ogg"}, 899 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_cif_768kbps_30fps_avc.mp4", 900 new int[]{6, 50, 77}, "mp4"}, 901 {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_cif_768kbps_30fps_vp9.mkv", 902 new int[]{8, 44}, "webm"}, 903 }); 904 } 905 TestOffsetPts(int outFormat, String file, int[] offsetIndices, String testName)906 public TestOffsetPts(int outFormat, String file, int[] offsetIndices, String testName) { 907 mOutFormat = outFormat; 908 mSrcFile = file; 909 mOffsetIndices = offsetIndices; 910 } 911 nativeTestOffsetPts(int format, String srcFile, String dstFile, int[] offsetIndices)912 private native boolean nativeTestOffsetPts(int format, String srcFile, String dstFile, 913 int[] offsetIndices); 914 915 @ApiTest(apis = "android.media.MediaMuxer#writeSampleData") 916 @Test testOffsetPresentationTime()917 public void testOffsetPresentationTime() throws IOException { 918 // values sohuld be in sync with nativeTestOffsetPts 919 final long[] OFFSET_TS_AUDIO_US = {-23220L, 0L, 200000L, 400000L}; 920 final long[] OFFSET_TS_VIDEO_US = {0L, 200000L, 400000L}; 921 Assume.assumeTrue(shouldRunTest(mOutFormat)); 922 Assume.assumeTrue("TODO(b/148978457)", 923 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 924 Assume.assumeTrue("TODO(b/148978457)", 925 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP); 926 Assume.assumeTrue("TODO(b/146423022)", 927 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM); 928 Assume.assumeTrue("TODO(b/146421018)", 929 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG); 930 MuxerTestHelper mediaInfo = new MuxerTestHelper(mInpPath); 931 for (long audioOffsetUs : OFFSET_TS_AUDIO_US) { 932 for (long videoOffsetUs : OFFSET_TS_VIDEO_US) { 933 for (int i = 0; i < mOffsetIndices.length; i++) { 934 mediaInfo.offsetTimeStamp(audioOffsetUs, videoOffsetUs, mOffsetIndices[i]); 935 } 936 MediaMuxer muxer = new MediaMuxer(mOutPath, mOutFormat); 937 mediaInfo.muxMedia(muxer); 938 muxer.release(); 939 MuxerTestHelper outInfo = new MuxerTestHelper(mOutPath); 940 if (!outInfo.isSubsetOf(mediaInfo)) { 941 String msg = String.format( 942 "testOffsetPresentationTime: inp: %s, fmt: %d, audioOffsetUs %d, " + 943 "videoOffsetUs %d ", 944 mSrcFile, mOutFormat, audioOffsetUs, videoOffsetUs); 945 fail(msg + "error! output != input"); 946 } 947 for (int i = mOffsetIndices.length - 1; i >= 0; i--) { 948 mediaInfo.offsetTimeStamp(-audioOffsetUs, -videoOffsetUs, 949 mOffsetIndices[i]); 950 } 951 } 952 } 953 } 954 955 @ApiTest(apis = "AMediaMuxer_writeSampleData") 956 @Test testOffsetPresentationTimeNative()957 public void testOffsetPresentationTimeNative() { 958 Assume.assumeTrue(shouldRunTest(mOutFormat)); 959 Assume.assumeTrue("TODO(b/148978457)", 960 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 961 Assume.assumeTrue("TODO(b/148978457)", 962 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP); 963 Assume.assumeTrue("TODO(b/146423022)", 964 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM); 965 Assume.assumeTrue("TODO(b/146421018)", 966 mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG); 967 assertTrue(nativeTestOffsetPts(mOutFormat, mInpPath, mOutPath, mOffsetIndices)); 968 } 969 } 970 971 /** 972 * Tests whether appending audio and/or video data to an existing media file works in all 973 * supported append modes. 974 */ 975 @ApiTest(apis = {"AMediaMuxer_append", "AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP", 976 "AMEDIAMUXER_APPEND_TO_EXISTING_DATA"}) 977 @FrameworkSpecificTest 978 @NonMainlineTest 979 @LargeTest 980 @RunWith(Parameterized.class) 981 public static class TestSimpleAppend { 982 private static final String LOG_TAG = MuxerTestHelper.class.getSimpleName(); 983 private String mSrcFile; 984 private String mInpPath; 985 private String mOutPath; 986 private int mOutFormat; 987 private int mTrackCount; 988 989 static { 990 System.loadLibrary("ctsmediav2muxer_jni"); 991 } 992 993 @Before prologue()994 public void prologue() throws IOException { 995 mInpPath = WorkDir.getMediaDirString() + mSrcFile; 996 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 997 } 998 999 @After epilogue()1000 public void epilogue() { 1001 new File(mOutPath).delete(); 1002 } 1003 1004 @Parameterized.Parameters(name = "{index}_{3}") input()1005 public static Collection<Object[]> input() { 1006 return Arrays.asList(new Object[][]{ 1007 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 1008 "bbb_stereo_48kHz_128kbps_aac.mp4", 1, "mp4"}, 1009 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 1010 "bbb_1920x1080_avc_high_l42.mp4", 1, "mp4"}, 1011 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 1012 "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", 2, "mp4"}, 1013 {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, 1014 "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", 2, "mp4"}, 1015 }); 1016 } 1017 TestSimpleAppend(int outFormat, String srcFile, int trackCount, String testName)1018 public TestSimpleAppend(int outFormat, String srcFile, int trackCount, String testName) { 1019 mOutFormat = outFormat; 1020 mSrcFile = srcFile; 1021 mTrackCount = trackCount; 1022 } 1023 nativeTestSimpleAppend(int outFormat, String srcPath, String outPath)1024 private native boolean nativeTestSimpleAppend(int outFormat, String srcPath, 1025 String outPath); 1026 nativeTestAppendGetTrackCount(String srcPath, int trackCount)1027 private native boolean nativeTestAppendGetTrackCount(String srcPath, int trackCount); 1028 nativeTestNoSamples(int outFormat, String srcPath, String outPath)1029 private native boolean nativeTestNoSamples(int outFormat, String srcPath, String outPath); 1030 nativeTestIgnoreLastGOPAppend(int outFormat, String srcPath, String outPath)1031 private native boolean nativeTestIgnoreLastGOPAppend(int outFormat, String srcPath, 1032 String outPath); 1033 nativeTestAppendGetTrackFormat(String srcPath)1034 private native boolean nativeTestAppendGetTrackFormat(String srcPath); 1035 1036 @Test testSimpleAppendNative()1037 public void testSimpleAppendNative() { 1038 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 1039 assertTrue(nativeTestSimpleAppend(mOutFormat, mInpPath, mOutPath)); 1040 } 1041 1042 @Test testAppendGetTrackCountNative()1043 public void testAppendGetTrackCountNative() { 1044 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 1045 assertTrue(nativeTestAppendGetTrackCount(mInpPath, mTrackCount)); 1046 } 1047 1048 @Test testAppendNoSamplesNative()1049 public void testAppendNoSamplesNative() { 1050 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 1051 assertTrue(nativeTestNoSamples(mOutFormat, mInpPath, mOutPath)); 1052 } 1053 1054 @Test testIgnoreLastGOPAppend()1055 public void testIgnoreLastGOPAppend() { 1056 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 1057 assertTrue(nativeTestIgnoreLastGOPAppend(mOutFormat, mInpPath, mOutPath)); 1058 } 1059 1060 @Test testAppendGetTrackFormatNative()1061 public void testAppendGetTrackFormatNative() { 1062 Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R); 1063 assertTrue(nativeTestAppendGetTrackFormat(mInpPath)); 1064 } 1065 } 1066 1067 1068 /** 1069 * Audio, Video Codecs support a variety of file-types/container formats. For example, 1070 * AAC-LC supports MPEG4, 3GPP. Vorbis supports OGG and WEBM. H.263 supports 3GPP and WEBM. 1071 * This test takes the output of a codec and muxes it in to all possible container formats. 1072 * The results are checked for inconsistencies with the requirements of CDD. 1073 */ 1074 @CddTest(requirements = {"5.1.3", "5.1.8"}) 1075 @FrameworkSpecificTest 1076 @NonMainlineTest 1077 @LargeTest 1078 @RunWith(Parameterized.class) 1079 public static class TestSimpleMux { 1080 private String mMediaType; 1081 private String mSrcFile; 1082 private String mInpPath; 1083 private String mOutPath; 1084 1085 static { 1086 System.loadLibrary("ctsmediav2muxer_jni"); 1087 } 1088 TestSimpleMux(String mediaType, String srcFile, String testName)1089 public TestSimpleMux(String mediaType, String srcFile, String testName) { 1090 mMediaType = mediaType; 1091 mSrcFile = srcFile; 1092 } 1093 1094 @Before prologue()1095 public void prologue() throws IOException { 1096 mInpPath = WorkDir.getMediaDirString() + mSrcFile; 1097 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 1098 } 1099 1100 @After epilogue()1101 public void epilogue() { 1102 new File(mOutPath).delete(); 1103 } 1104 doesCodecRequireCSD(String mediaType)1105 private boolean doesCodecRequireCSD(String mediaType) { 1106 return (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AVC) 1107 || mediaType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC) 1108 || mediaType.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4) 1109 || mediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC)); 1110 } 1111 nativeTestSimpleMux(String srcPath, String outPath, String mediaType, String selector)1112 private native boolean nativeTestSimpleMux(String srcPath, String outPath, String mediaType, 1113 String selector); 1114 nativeTestSimpleAppend(String srcPath, String outPath, String mediaType, String selector)1115 private native boolean nativeTestSimpleAppend(String srcPath, String outPath, 1116 String mediaType, String selector); 1117 1118 @Parameterized.Parameters(name = "{index}_{2}") input()1119 public static Collection<Object[]> input() { 1120 return Arrays.asList(new Object[][]{ 1121 // Video Codecs 1122 {MediaFormat.MIMETYPE_VIDEO_H263, 1123 "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", "h263"}, 1124 {MediaFormat.MIMETYPE_VIDEO_AVC, 1125 "bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4", "avc"}, 1126 {MediaFormat.MIMETYPE_VIDEO_HEVC, 1127 "bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_opus.mp4", "hevc"}, 1128 {MediaFormat.MIMETYPE_VIDEO_MPEG4, 1129 "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", "mpeg4"}, 1130 {MediaFormat.MIMETYPE_VIDEO_VP8, 1131 "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm", "vp8"}, 1132 {MediaFormat.MIMETYPE_VIDEO_VP9, 1133 "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm", "vp9"}, 1134 // Audio Codecs 1135 {MediaFormat.MIMETYPE_AUDIO_AAC, 1136 "bbb_stereo_48kHz_128kbps_aac.mp4", "aac"}, 1137 {MediaFormat.MIMETYPE_AUDIO_AMR_NB, 1138 "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", "amrnb"}, 1139 {MediaFormat.MIMETYPE_AUDIO_AMR_WB, 1140 "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", "amrwb"}, 1141 {MediaFormat.MIMETYPE_AUDIO_OPUS, 1142 "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm", "opus"}, 1143 {MediaFormat.MIMETYPE_AUDIO_VORBIS, 1144 "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm", "vorbis"}, 1145 // Metadata 1146 {"application/gyro", 1147 "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp", 1148 "gyro-non-compliant"}, 1149 {"application/gyro", 1150 "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp", 1151 "gyro-compliant"}, 1152 }); 1153 } 1154 1155 @Test testSimpleMux()1156 public void testSimpleMux() throws IOException { 1157 Assume.assumeTrue("TODO(b/146421018)", 1158 !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_OPUS)); 1159 Assume.assumeTrue("TODO(b/146923287)", 1160 !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS)); 1161 MuxerTestHelper mediaInfo = new MuxerTestHelper(mInpPath, mMediaType); 1162 assertEquals("error! unexpected track count", 1, mediaInfo.getTrackCount()); 1163 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; format++) { 1164 if (!shouldRunTest(format)) continue; 1165 // TODO(b/146923551) 1166 if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM) continue; 1167 String msg = String.format("testSimpleMux: inp: %s, mediaType: %s, fmt: %d ", 1168 mSrcFile, mMediaType, format); 1169 MediaMuxer muxer = new MediaMuxer(mOutPath, format); 1170 try { 1171 mediaInfo.muxMedia(muxer); 1172 MuxerTestHelper outInfo = new MuxerTestHelper(mOutPath); 1173 if (!mediaInfo.isSubsetOf(outInfo)) { 1174 fail(msg + "error! output != clone(input)"); 1175 } 1176 } catch (Exception e) { 1177 if (isMediaTypeContainerPairValid(mMediaType, format)) { 1178 fail(msg + "error! incompatible mediaType and output format"); 1179 } 1180 } finally { 1181 muxer.release(); 1182 } 1183 } 1184 } 1185 1186 @Test testSimpleMuxNative()1187 public void testSimpleMuxNative() { 1188 Assume.assumeTrue("TODO(b/146421018)", 1189 !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_OPUS)); 1190 Assume.assumeTrue("TODO(b/146923287)", 1191 !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS)); 1192 assertTrue(nativeTestSimpleMux(mInpPath, mOutPath, mMediaType, selector)); 1193 } 1194 1195 /* Does MediaMuxer throw IllegalStateException on missing codec specific data when required. 1196 * Check if relevant exception is thrown for AAC, AVC, HEVC, and MPEG4 1197 * codecs that require CSD in MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4. 1198 * TODO(b/156767190): Need to evaluate what all codecs need CSD and also what all formats 1199 * can contain these codecs, and add test cases accordingly. 1200 * TODO(b/156767190): Add similar tests in the native side/NDK as well. 1201 * TODO(b/156767190): Make a separate class, like TestNoCSDMux, instead of being part of 1202 * TestSimpleMux? 1203 */ 1204 @ApiTest(apis = "android.media.MediaMuxer#addTrack") 1205 @Test testNoCSDMux()1206 public void testNoCSDMux() throws IOException { 1207 Assume.assumeTrue(doesCodecRequireCSD(mMediaType)); 1208 MuxerTestHelper mediaInfo = new MuxerTestHelper(mInpPath, true); 1209 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; format++) { 1210 // TODO(b/156767190) 1211 if(format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue; 1212 MediaMuxer muxer = new MediaMuxer(mOutPath, format); 1213 Exception expected = null; 1214 String msg = String.format("testNoCSDMux: inp: %s, mediaType %s, fmt: %s", mSrcFile, 1215 mMediaType, formatStringPair.get(format)); 1216 try { 1217 mediaInfo.muxMedia(muxer); 1218 } catch (IllegalStateException e) { 1219 expected = e; 1220 } catch (Exception e) { 1221 fail(msg + ", unexpected exception:" + e.getMessage()); 1222 } finally { 1223 assertNotNull(msg, expected); 1224 muxer.release(); 1225 } 1226 } 1227 } 1228 } 1229 1230 @ApiTest(apis = {"android.media.MediaMuxer#start", "android.media.MediaMuxer#stop"}) 1231 @FrameworkSpecificTest 1232 @NonMainlineTest 1233 @LargeTest 1234 @RunWith(Parameterized.class) 1235 public static class TestAddEmptyTracks { 1236 private final List<String> mMediaTypeListforTypeMp4 = 1237 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263, 1238 MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC, 1239 MediaFormat.MIMETYPE_AUDIO_AAC, MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC, 1240 MediaFormat.MIMETYPE_TEXT_SUBRIP); 1241 private final List<String> mMediaTypeListforTypeWebm = 1242 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_VP8, MediaFormat.MIMETYPE_VIDEO_VP9, 1243 MediaFormat.MIMETYPE_AUDIO_VORBIS, MediaFormat.MIMETYPE_AUDIO_OPUS); 1244 private final List<String> mMediaTypeListforType3gp = 1245 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263, 1246 MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_AUDIO_AAC, 1247 MediaFormat.MIMETYPE_AUDIO_AMR_NB, MediaFormat.MIMETYPE_AUDIO_AMR_WB); 1248 private final List<String> mMediaTypeListforTypeOgg = 1249 Arrays.asList(MediaFormat.MIMETYPE_AUDIO_OPUS); 1250 private String mMediaType; 1251 private String mOutPath; 1252 TestAddEmptyTracks(String mediaType)1253 public TestAddEmptyTracks(String mediaType) { 1254 mMediaType = mediaType; 1255 } 1256 1257 @Before prologue()1258 public void prologue() throws IOException { 1259 mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath(); 1260 } 1261 1262 @After epilogue()1263 public void epilogue() { 1264 new File(mOutPath).delete(); 1265 } 1266 isMediaTypeContainerPairValid(int format)1267 private boolean isMediaTypeContainerPairValid(int format) { 1268 boolean result = false; 1269 if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) 1270 result = mMediaTypeListforTypeMp4.contains(mMediaType); 1271 else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM) { 1272 return mMediaTypeListforTypeWebm.contains(mMediaType); 1273 } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) { 1274 result = mMediaTypeListforType3gp.contains(mMediaType); 1275 } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG) { 1276 result = mMediaTypeListforTypeOgg.contains(mMediaType); 1277 } 1278 return result; 1279 } 1280 1281 @Parameterized.Parameters(name = "{index}_{0}") input()1282 public static Collection<Object[]> input() { 1283 return Arrays.asList(new Object[][]{ 1284 // Video 1285 {MediaFormat.MIMETYPE_VIDEO_H263}, 1286 {MediaFormat.MIMETYPE_VIDEO_AVC}, 1287 {MediaFormat.MIMETYPE_VIDEO_HEVC}, 1288 {MediaFormat.MIMETYPE_VIDEO_MPEG4}, 1289 {MediaFormat.MIMETYPE_VIDEO_VP8}, 1290 {MediaFormat.MIMETYPE_VIDEO_VP9}, 1291 // Audio 1292 {MediaFormat.MIMETYPE_AUDIO_AAC}, 1293 {MediaFormat.MIMETYPE_AUDIO_AMR_NB}, 1294 {MediaFormat.MIMETYPE_AUDIO_AMR_WB}, 1295 {MediaFormat.MIMETYPE_AUDIO_OPUS}, 1296 {MediaFormat.MIMETYPE_AUDIO_VORBIS}, 1297 // Metadata 1298 {MediaFormat.MIMETYPE_TEXT_SUBRIP}, 1299 // Image 1300 {MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC} 1301 }); 1302 } 1303 1304 @Test testEmptyVideoTrack()1305 public void testEmptyVideoTrack() { 1306 if (!mMediaType.startsWith("video/")) return; 1307 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) { 1308 if (!isMediaTypeContainerPairValid(format)) continue; 1309 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue; 1310 try { 1311 MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format); 1312 MediaFormat mediaFormat = new MediaFormat(); 1313 mediaFormat.setString(MediaFormat.KEY_MIME, mMediaType); 1314 mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96); 1315 mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128); 1316 mediaMuxer.addTrack(mediaFormat); 1317 mediaMuxer.start(); 1318 mediaMuxer.stop(); 1319 mediaMuxer.release(); 1320 } catch (Exception e) { 1321 fail("testEmptyVideoTrack : unexpected exception : " + e.getMessage()); 1322 } 1323 } 1324 } 1325 1326 @Test testEmptyAudioTrack()1327 public void testEmptyAudioTrack() { 1328 if (!mMediaType.startsWith("audio/")) return; 1329 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) { 1330 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue; 1331 if (!isMediaTypeContainerPairValid(format)) continue; 1332 try { 1333 MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format); 1334 MediaFormat mediaFormat = new MediaFormat(); 1335 mediaFormat.setString(MediaFormat.KEY_MIME, mMediaType); 1336 if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { 1337 mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000); 1338 } else { 1339 mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000); 1340 } 1341 mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 1342 mediaMuxer.addTrack(mediaFormat); 1343 mediaMuxer.start(); 1344 mediaMuxer.stop(); 1345 mediaMuxer.release(); 1346 } catch (Exception e) { 1347 fail("testEmptyAudioTrack : unexpected exception : " + e.getMessage()); 1348 } 1349 } 1350 } 1351 1352 @Test testEmptyMetaDataTrack()1353 public void testEmptyMetaDataTrack() { 1354 if (!mMediaType.startsWith("application/")) return; 1355 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) { 1356 if (!isMediaTypeContainerPairValid(format)) continue; 1357 try { 1358 MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format); 1359 MediaFormat mediaFormat = new MediaFormat(); 1360 mediaFormat.setString(MediaFormat.KEY_MIME, mMediaType); 1361 mediaMuxer.addTrack(mediaFormat); 1362 mediaMuxer.start(); 1363 mediaMuxer.stop(); 1364 mediaMuxer.release(); 1365 } catch (Exception e) { 1366 fail("testEmptyMetaDataTrack : unexpected exception : " + e.getMessage()); 1367 } 1368 } 1369 } 1370 1371 @Test testEmptyImageTrack()1372 public void testEmptyImageTrack() { 1373 if (!mMediaType.startsWith("image/")) return; 1374 for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) { 1375 if (!isMediaTypeContainerPairValid(format)) continue; 1376 try { 1377 MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format); 1378 MediaFormat mediaFormat = new MediaFormat(); 1379 mediaFormat.setString(MediaFormat.KEY_MIME, mMediaType); 1380 mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96); 1381 mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128); 1382 mediaMuxer.addTrack(mediaFormat); 1383 mediaMuxer.start(); 1384 mediaMuxer.stop(); 1385 mediaMuxer.release(); 1386 } catch (Exception e) { 1387 fail("testEmptyImageTrack : unexpected exception : " + e.getMessage()); 1388 } 1389 } 1390 } 1391 } 1392 } 1393