1 /* 2 * Copyright (C) 2022 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.common.cts; 18 19 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; 20 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible; 21 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010; 22 import static android.media.codec.Flags.subsessionMetrics; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.graphics.ImageFormat; 31 import android.media.Image; 32 import android.media.MediaCodec; 33 import android.media.MediaCodecInfo; 34 import android.media.MediaExtractor; 35 import android.media.MediaFormat; 36 import android.os.PersistableBundle; 37 import android.util.Log; 38 import android.util.Pair; 39 40 import com.android.compatibility.common.util.Preconditions; 41 42 import org.junit.After; 43 import org.junit.Before; 44 45 import java.io.IOException; 46 import java.nio.ByteBuffer; 47 import java.util.ArrayList; 48 import java.util.Locale; 49 50 /** 51 * Wrapper class for trying and testing mediacodec decoder components. 52 */ 53 public class CodecDecoderTestBase extends CodecTestBase { 54 private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName(); 55 56 protected final String mTestFile; 57 protected boolean mIsInterlaced; 58 protected boolean mSkipChecksumVerification; 59 60 protected final ArrayList<ByteBuffer> mCsdBuffers; 61 protected int mCurrCsdIdx; 62 63 protected final ByteBuffer mFlatBuffer = ByteBuffer.allocate(4 * Integer.BYTES); 64 65 protected MediaExtractor mExtractor; 66 CodecDecoderTestBase(String codecName, String mediaType, String testFile, String allTestParams)67 public CodecDecoderTestBase(String codecName, String mediaType, String testFile, 68 String allTestParams) { 69 super(codecName, mediaType, allTestParams); 70 mTestFile = testFile; 71 mCsdBuffers = new ArrayList<>(); 72 } 73 74 @Before setUpCodecDecoderTestBase()75 public void setUpCodecDecoderTestBase() { 76 assertTrue("Testing a mediaType that is neither audio nor video is not supported \n" 77 + mTestConfig, mIsAudio || mIsVideo); 78 } 79 80 @After tearDownCodecDecoderTestBase()81 public void tearDownCodecDecoderTestBase() { 82 if (mExtractor != null) { 83 mExtractor.release(); 84 mExtractor = null; 85 } 86 } 87 getMaxSampleSizeForMediaType(String fileName, String mediaType)88 public static int getMaxSampleSizeForMediaType(String fileName, String mediaType) 89 throws IOException { 90 Preconditions.assertTestFileExists(fileName); 91 int maxSampleSize = 0; 92 MediaExtractor extractor = new MediaExtractor(); 93 extractor.setDataSource(fileName); 94 for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) { 95 MediaFormat format = extractor.getTrackFormat(trackID); 96 if (mediaType.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) { 97 extractor.selectTrack(trackID); 98 if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) { 99 maxSampleSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); 100 } else { 101 int size; 102 while ((size = (int) extractor.getSampleSize()) != -1) { 103 maxSampleSize = Math.max(maxSampleSize, size); 104 extractor.advance(); 105 } 106 } 107 extractor.release(); 108 return maxSampleSize; 109 } 110 } 111 fail("No track with mediaType: " + mediaType + " found in file: " + fileName + "\n"); 112 return maxSampleSize; 113 } 114 setUpSource(String srcFile)115 protected MediaFormat setUpSource(String srcFile) throws IOException { 116 Preconditions.assertTestFileExists(srcFile); 117 mExtractor = new MediaExtractor(); 118 mExtractor.setDataSource(srcFile); 119 for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) { 120 MediaFormat format = mExtractor.getTrackFormat(trackID); 121 if (mMediaType.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) { 122 // This is required for some mlaw and alaw test vectors where access unit size is 123 // exceeding default max input size 124 if (mMediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) 125 || mMediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { 126 format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 127 getMaxSampleSizeForMediaType(srcFile, mMediaType)); 128 } 129 mExtractor.selectTrack(trackID); 130 if (mIsVideo) { 131 ArrayList<MediaFormat> formatList = new ArrayList<>(); 132 formatList.add(format); 133 boolean selectHBD = doesAnyFormatHaveHDRProfile(mMediaType, formatList); 134 if (!selectHBD && srcFile.contains("10bit")) { 135 selectHBD = true; 136 } 137 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 138 getColorFormat(mCodecName, mMediaType, mSurface != null, selectHBD)); 139 if (selectHBD && (format.getInteger(MediaFormat.KEY_COLOR_FORMAT) 140 != COLOR_FormatYUVP010)) { 141 mSkipChecksumVerification = true; 142 } 143 144 if ((format.getInteger(MediaFormat.KEY_COLOR_FORMAT) != COLOR_FormatYUVP010) 145 && selectHBD && mSurface == null) { 146 // Codecs that do not advertise P010 on devices with VNDK version < T, do 147 // not support decoding high bit depth clips when color format is set to 148 // COLOR_FormatYUV420Flexible in byte buffer mode. Since byte buffer mode 149 // for high bit depth decoding wasn't tested prior to Android T, skip this 150 // when device is older 151 assumeTrue("Skipping High Bit Depth tests on VNDK < T", VNDK_IS_AT_LEAST_T); 152 } 153 } 154 // TODO: determine this from the extractor format when it becomes exposed. 155 mIsInterlaced = srcFile.contains("_interlaced_"); 156 return format; 157 } 158 } 159 fail("No track with mediaType: " + mMediaType + " found in file: " + srcFile + "\n" 160 + mTestConfig + mTestEnv); 161 return null; 162 } 163 doOutputFormatChecks(MediaFormat defaultFormat, MediaFormat configuredFormat)164 protected void doOutputFormatChecks(MediaFormat defaultFormat, MediaFormat configuredFormat) { 165 String msg = String.format("Input test file format is not same as default format of" 166 + " component, but test did not receive INFO_OUTPUT_FORMAT_CHANGED signal" 167 + ".\nInput file format is :- %s \nDefault format is :- %s \n", 168 configuredFormat, defaultFormat); 169 assertTrue(msg + mTestConfig + mTestEnv, 170 mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() : 171 mSignalledOutFormatChanged); 172 173 MediaFormat outputFormat = 174 mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat; 175 msg = String.format("Configured input format and received output format are " 176 + "not similar. \nConfigured Input format is :- %s \nReceived Output " 177 + "format is :- %s \n", configuredFormat, outputFormat); 178 assertTrue(msg + mTestConfig + mTestEnv, isFormatSimilar(configuredFormat, outputFormat)); 179 } 180 getColorFormat(String name, String mediaType, boolean surfaceMode, boolean hbdMode)181 int getColorFormat(String name, String mediaType, boolean surfaceMode, boolean hbdMode) { 182 if (surfaceMode) return COLOR_FormatSurface; 183 if (hbdMode) { 184 MediaCodecInfo.CodecCapabilities cap = null; 185 for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_ALL.getCodecInfos()) { 186 if (name.equals(codecInfo.getName())) { 187 cap = codecInfo.getCapabilitiesForType(mediaType); 188 break; 189 } 190 } 191 assertNotNull("did not receive capabilities for codec: " + name + ", media type: " 192 + mediaType + "\n" + mTestConfig + mTestEnv, cap); 193 for (int c : cap.colorFormats) { 194 if (c == COLOR_FormatYUVP010) { 195 return c; 196 } 197 } 198 } 199 return COLOR_FormatYUV420Flexible; 200 } 201 hasCSD(MediaFormat format)202 public static boolean hasCSD(MediaFormat format) { 203 return format.containsKey("csd-0"); 204 } 205 flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio)206 protected void flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio) { 207 if (isAudio) { 208 mFlatBuffer.putInt(info.size); 209 } 210 mFlatBuffer.putInt(info.flags & ~MediaCodec.BUFFER_FLAG_END_OF_STREAM) 211 .putLong(info.presentationTimeUs); 212 mFlatBuffer.flip(); 213 } 214 enqueueCodecConfig(int bufferIndex)215 void enqueueCodecConfig(int bufferIndex) { 216 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 217 ByteBuffer csdBuffer = mCsdBuffers.get(mCurrCsdIdx); 218 inputBuffer.put((ByteBuffer) csdBuffer.rewind()); 219 mCodec.queueInputBuffer(bufferIndex, 0, csdBuffer.limit(), 0, 220 MediaCodec.BUFFER_FLAG_CODEC_CONFIG); 221 if (ENABLE_LOGS) { 222 Log.v(LOG_TAG, "queued csd: id: " + bufferIndex + " size: " + csdBuffer.limit()); 223 } 224 } 225 enqueueInput(int bufferIndex)226 protected void enqueueInput(int bufferIndex) { 227 if (mExtractor.getSampleSize() < 0) { 228 enqueueEOS(bufferIndex); 229 } else { 230 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 231 mExtractor.readSampleData(inputBuffer, 0); 232 int size = (int) mExtractor.getSampleSize(); 233 long pts = mExtractor.getSampleTime(); 234 int extractorFlags = mExtractor.getSampleFlags(); 235 int codecFlags = 0; 236 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) { 237 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME; 238 } 239 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) { 240 codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME; 241 } 242 if (!mExtractor.advance() && mSignalEOSWithLastFrame) { 243 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 244 mSawInputEOS = true; 245 } 246 if (ENABLE_LOGS) { 247 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts 248 + " flags: " + codecFlags); 249 } 250 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags); 251 if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG 252 | MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) { 253 mOutputBuff.saveInPTS(pts); 254 mInputCount++; 255 } 256 } 257 } 258 enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info)259 protected void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) { 260 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 261 buffer.position(info.offset); 262 for (int i = 0; i < info.size; i++) { 263 inputBuffer.put(buffer.get()); 264 } 265 if (ENABLE_LOGS) { 266 Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " 267 + info.size + " timestamp: " + info.presentationTimeUs); 268 } 269 mCodec.queueInputBuffer(bufferIndex, 0, info.size, info.presentationTimeUs, 270 info.flags); 271 if (info.size > 0 && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) 272 && ((info.flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) { 273 mOutputBuff.saveInPTS(info.presentationTimeUs); 274 mInputCount++; 275 } 276 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 277 mSawInputEOS = true; 278 } 279 } 280 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)281 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 282 if (info.size > 0 && mSaveToMem) { 283 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 284 flattenBufferInfo(info, mIsAudio); 285 mOutputBuff.checksum(mFlatBuffer, mFlatBuffer.limit()); 286 if (mIsAudio) { 287 mOutputBuff.checksum(buf, info); 288 mOutputBuff.saveToMemory(buf, info); 289 } else { 290 // tests both getOutputImage and getOutputBuffer. Can do time division 291 // multiplexing but lets allow it for now 292 Image img = mCodec.getOutputImage(bufferIndex); 293 assertNotNull("CPU-read via ImageReader API is not available", img); 294 mOutputBuff.checksum(img); 295 int imgFormat = img.getFormat(); 296 int bytesPerSample = (ImageFormat.getBitsPerPixel(imgFormat) * 2) / (8 * 3); 297 298 MediaFormat format = mCodec.getOutputFormat(); 299 buf = mCodec.getOutputBuffer(bufferIndex); 300 int width = format.getInteger(MediaFormat.KEY_WIDTH); 301 int height = format.getInteger(MediaFormat.KEY_HEIGHT); 302 int stride = format.getInteger(MediaFormat.KEY_STRIDE); 303 mOutputBuff.checksum(buf, info.size, width, height, stride, bytesPerSample); 304 } 305 } 306 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 307 mSawOutputEOS = true; 308 } 309 if (ENABLE_LOGS) { 310 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " 311 + info.size + " timestamp: " + info.presentationTimeUs); 312 } 313 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 314 mOutputBuff.saveOutPTS(info.presentationTimeUs); 315 mOutputCount++; 316 } 317 mCodec.releaseOutputBuffer(bufferIndex, false); 318 } 319 doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)320 protected void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list) 321 throws InterruptedException { 322 int frameCount = 0; 323 if (mIsCodecInAsyncMode) { 324 // output processing after queuing EOS is done in waitForAllOutputs() 325 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) { 326 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 327 if (element != null) { 328 int bufferID = element.first; 329 MediaCodec.BufferInfo info = element.second; 330 if (info != null) { 331 dequeueOutput(bufferID, info); 332 } else { 333 enqueueInput(bufferID, buffer, list.get(frameCount)); 334 frameCount++; 335 } 336 } 337 } 338 } else { 339 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 340 // output processing after queuing EOS is done in waitForAllOutputs() 341 while (!mSawInputEOS && frameCount < list.size()) { 342 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 343 if (outputBufferId >= 0) { 344 dequeueOutput(outputBufferId, outInfo); 345 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 346 mOutFormat = mCodec.getOutputFormat(); 347 mSignalledOutFormatChanged = true; 348 } 349 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 350 if (inputBufferId != -1) { 351 enqueueInput(inputBufferId, buffer, list.get(frameCount)); 352 frameCount++; 353 } 354 } 355 } 356 } 357 queueCodecConfig()358 protected void queueCodecConfig() throws InterruptedException { 359 if (mIsCodecInAsyncMode) { 360 for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size(); 361 mCurrCsdIdx++) { 362 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput(); 363 if (element != null) { 364 enqueueCodecConfig(element.first); 365 } 366 } 367 } else { 368 for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) { 369 enqueueCodecConfig(mCodec.dequeueInputBuffer(-1)); 370 } 371 } 372 } 373 374 @Override validateTestState()375 protected void validateTestState() { 376 super.validateTestState(); 377 if (!mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts)) { 378 fail("Output timestamps are not strictly increasing \n" + mTestConfig + mTestEnv 379 + mOutputBuff.getErrMsg()); 380 } 381 if (mIsVideo) { 382 // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders 383 // produce multiple progressive frames?) For now, do not verify timestamps. 384 if (!mIsInterlaced && !mOutputBuff.isOutPtsListIdenticalToInpPtsList(false)) { 385 fail("Input pts list and Output pts list are not identical ]\n" + mTestConfig 386 + mTestEnv + mOutputBuff.getErrMsg()); 387 } 388 if (IS_AT_LEAST_B && subsessionMetrics()) { 389 int min = mAsyncHandle.getMinExpectedMetricsFlushCount(); 390 int got = mAsyncHandle.getActualMetricsFlushCount(); 391 if (min > got) { 392 fail(String.format(Locale.getDefault(), 393 "min expected metrics flush count is %d, but got %d \n" + mTestConfig 394 + mTestEnv, min, got)); 395 } 396 } 397 } 398 } 399 400 /** 401 * This function decodes the input file using the component given and stores the result in 402 * OutputManager. By default the decoded frames pts information is stored. This is used for 403 * general validation. Storing decoded frames is dependent on saveToMem argument and 404 * mediaType. 405 * <ul> 406 * <li>If saveToMem is true and mediaType is audio, then the raw output is stored in a 407 * byte buffer.</li> 408 * <li>If saveToMem is true and mediaType is video, it is unrealistic to store all the 409 * frames in raw format as it occupies huge space. So, for them their checksum is 410 * preserved.</li> 411 * </ul> 412 */ decodeToMemory(String file, String decoder, OutputManager outputBuff, boolean saveToMem, long pts, int mode, int frameLimit, boolean isAsync, boolean signalledEos)413 public void decodeToMemory(String file, String decoder, OutputManager outputBuff, 414 boolean saveToMem, long pts, int mode, int frameLimit, boolean isAsync, 415 boolean signalledEos) throws IOException, InterruptedException { 416 mSaveToMem = saveToMem; 417 mOutputBuff = outputBuff; 418 mCodec = MediaCodec.createByCodecName(decoder); 419 MediaFormat format = setUpSource(file); 420 configureCodec(format, isAsync, signalledEos, false); 421 mCodec.start(); 422 mExtractor.seekTo(pts, mode); 423 doWork(frameLimit); 424 queueEOS(); 425 waitForAllOutputs(); 426 mCodec.stop(); 427 mCodec.release(); 428 mExtractor.release(); 429 mSaveToMem = false; 430 } 431 decodeToMemory(String file, String decoder, OutputManager outputBuff, long pts, int mode, int frameLimit, boolean isAsync, boolean signalledEos)432 public void decodeToMemory(String file, String decoder, OutputManager outputBuff, long pts, 433 int mode, int frameLimit, boolean isAsync, boolean signalledEos) 434 throws IOException, InterruptedException { 435 decodeToMemory(file, decoder, outputBuff, true, pts, mode, frameLimit, isAsync, 436 signalledEos); 437 } 438 decodeToMemory(String file, String decoder, OutputManager outputBuff, long pts, int mode, int frameLimit)439 public void decodeToMemory(String file, String decoder, OutputManager outputBuff, long pts, 440 int mode, int frameLimit) throws IOException, InterruptedException { 441 decodeToMemory(file, decoder, outputBuff, pts, mode, frameLimit, false, true); 442 } 443 decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)444 public void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit) 445 throws IOException, InterruptedException { 446 decodeToMemory(file, decoder, new OutputManager(), pts, mode, frameLimit); 447 } 448 decodeToMemory(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list, MediaFormat format, String decoder)449 public void decodeToMemory(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list, 450 MediaFormat format, String decoder) throws IOException, InterruptedException { 451 mSaveToMem = true; 452 mOutputBuff = new OutputManager(); 453 mCodec = MediaCodec.createByCodecName(decoder); 454 configureCodec(format, false, true, false); 455 mCodec.start(); 456 doWork(buffer, list); 457 queueEOS(); 458 waitForAllOutputs(); 459 mCodec.stop(); 460 mCodec.release(); 461 mSaveToMem = false; 462 } 463 464 @Override validateMetrics(String decoder, MediaFormat format)465 protected PersistableBundle validateMetrics(String decoder, MediaFormat format) { 466 PersistableBundle metrics = super.validateMetrics(decoder, format); 467 assertEquals("error! metrics#MetricsConstants.MIME_TYPE is not as expected \n" + mTestConfig 468 + mTestEnv, metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE), mMediaType); 469 assertEquals("error! metrics#MetricsConstants.ENCODER is not as expected \n" + mTestConfig 470 + mTestEnv, 0, metrics.getInt(MediaCodec.MetricsConstants.ENCODER)); 471 return metrics; 472 } 473 validateColorAspects(int range, int standard, int transfer, boolean ignoreColorBox)474 public void validateColorAspects(int range, int standard, int transfer, boolean ignoreColorBox) 475 throws IOException, InterruptedException { 476 Preconditions.assertTestFileExists(mTestFile); 477 mOutputBuff = new OutputManager(); 478 MediaFormat format = setUpSource(mTestFile); 479 if (ignoreColorBox) { 480 format.removeKey(MediaFormat.KEY_COLOR_RANGE); 481 format.removeKey(MediaFormat.KEY_COLOR_STANDARD); 482 format.removeKey(MediaFormat.KEY_COLOR_TRANSFER); 483 } 484 mCodec = MediaCodec.createByCodecName(mCodecName); 485 configureCodec(format, true, true, false); 486 mCodec.start(); 487 doWork(1); 488 queueEOS(); 489 waitForAllOutputs(); 490 validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer); 491 mCodec.stop(); 492 mCodec.release(); 493 mExtractor.release(); 494 } 495 } 496