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