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 org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 23 import android.graphics.ImageFormat; 24 import android.graphics.Rect; 25 import android.media.AudioFormat; 26 import android.media.Image; 27 import android.media.MediaCodec; 28 29 import java.io.File; 30 import java.io.FileOutputStream; 31 import java.nio.ByteBuffer; 32 import java.nio.ByteOrder; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.Locale; 37 import java.util.Objects; 38 import java.util.stream.IntStream; 39 import java.util.zip.CRC32; 40 41 /** 42 * Class to store the output received from mediacodec components. The dequeueOutput() call sends 43 * compressed/decoded bytes of data and their corresponding timestamp information. This is stored 44 * in memory and outPtsList fields of this class. For video decoders, the decoded information can 45 * be overwhelming as it is uncompressed YUV. For them we compute the CRC32 checksum of the 46 * output image and buffer and store it instead. 47 * <p> 48 * ByteBuffer output of encoder/decoder components can be written to disk by setting ENABLE_DUMP 49 * to true. Exercise CAUTION while running tests with ENABLE_DUMP set to true as this will crowd 50 * the storage with files. These files are configured to be deleted on exit. So, in order to see 51 * the captured output, File.deleteOnExit() needs to be be commented. Also it might be necessary 52 * to set option name="cleanup-apks" to "false" in AndroidTest.xml. 53 */ 54 public class OutputManager { 55 private static final String LOG_TAG = OutputManager.class.getSimpleName(); 56 private static final boolean ENABLE_DUMP = false; 57 58 private byte[] mMemory; 59 private int mMemIndex; 60 private final CRC32 mCrc32UsingImage; 61 private final CRC32 mCrc32UsingBuffer; 62 private final ArrayList<Long> mInpPtsList; 63 private final ArrayList<Long> mOutPtsList; 64 private final StringBuilder mErrorLogs; 65 private final StringBuilder mSharedErrorLogs; 66 private File mOutFileYuv; 67 private boolean mAppendToYuvFile; 68 private File mOutFileY; 69 private boolean mAppendToYFile; 70 private File mOutFileDefault; 71 OutputManager()72 public OutputManager() { 73 this(new StringBuilder()); 74 } 75 OutputManager(StringBuilder sharedErrorLogs)76 public OutputManager(StringBuilder sharedErrorLogs) { 77 mMemory = new byte[1024]; 78 mMemIndex = 0; 79 mCrc32UsingImage = new CRC32(); 80 mCrc32UsingBuffer = new CRC32(); 81 mInpPtsList = new ArrayList<>(); 82 mOutPtsList = new ArrayList<>(); 83 mErrorLogs = new StringBuilder( 84 "################## Error Details ####################\n"); 85 mSharedErrorLogs = sharedErrorLogs; 86 } 87 saveInPTS(long pts)88 public void saveInPTS(long pts) { 89 // Add only unique timeStamp, discarding any duplicate frame / non-display frame 90 if (!mInpPtsList.contains(pts)) { 91 mInpPtsList.add(pts); 92 } 93 } 94 saveOutPTS(long pts)95 public void saveOutPTS(long pts) { 96 mOutPtsList.add(pts); 97 } 98 isPtsStrictlyIncreasing(long lastPts)99 public boolean isPtsStrictlyIncreasing(long lastPts) { 100 boolean res = true; 101 for (int i = 0; i < mOutPtsList.size(); i++) { 102 if (lastPts < mOutPtsList.get(i)) { 103 lastPts = mOutPtsList.get(i); 104 } else { 105 mErrorLogs.append("Timestamp values are not strictly increasing. \n"); 106 mErrorLogs.append("Frame indices around which timestamp values decreased :- \n"); 107 for (int j = Math.max(0, i - 3); j < Math.min(mOutPtsList.size(), i + 3); j++) { 108 if (j == 0) { 109 mErrorLogs.append(String.format(Locale.getDefault(), 110 "pts of frame idx -1 is %d \n", lastPts)); 111 } 112 mErrorLogs.append(String.format(Locale.getDefault(), 113 "pts of frame idx %d is %d \n", j, mOutPtsList.get(j))); 114 } 115 res = false; 116 break; 117 } 118 } 119 return res; 120 } 121 arePtsListsIdentical(ArrayList<Long> refList, ArrayList<Long> testList, StringBuilder msg)122 static boolean arePtsListsIdentical(ArrayList<Long> refList, ArrayList<Long> testList, 123 StringBuilder msg) { 124 boolean res = true; 125 if (refList.size() != testList.size()) { 126 msg.append("Reference and test timestamps list sizes are not identical \n"); 127 msg.append(String.format(Locale.getDefault(), "reference pts list size is %d \n", 128 refList.size())); 129 msg.append(String.format(Locale.getDefault(), "test pts list size is %d \n", 130 testList.size())); 131 res = false; 132 } 133 for (int i = 0; i < Math.min(refList.size(), testList.size()); i++) { 134 if (!Objects.equals(refList.get(i), testList.get(i))) { 135 msg.append(String.format(Locale.getDefault(), 136 "Frame idx %d, ref pts %dus, test pts %dus \n", i, refList.get(i), 137 testList.get(i))); 138 res = false; 139 } 140 } 141 if (refList.size() < testList.size()) { 142 for (int i = refList.size(); i < testList.size(); i++) { 143 msg.append(String.format(Locale.getDefault(), 144 "Frame idx %d, ref pts EMPTY, test pts %dus \n", i, testList.get(i))); 145 } 146 } else if (refList.size() > testList.size()) { 147 for (int i = testList.size(); i < refList.size(); i++) { 148 msg.append(String.format(Locale.getDefault(), 149 "Frame idx %d, ref pts %dus, test pts EMPTY \n", i, refList.get(i))); 150 } 151 } 152 if (!res) { 153 msg.append("Are frames for which timestamps differ between reference and test. \n"); 154 } 155 return res; 156 } 157 isOutPtsListIdenticalToInpPtsList(boolean requireSorting)158 public boolean isOutPtsListIdenticalToInpPtsList(boolean requireSorting) { 159 ArrayList<Long> inpPtsListCopy = new ArrayList<>(mInpPtsList); 160 Collections.sort(inpPtsListCopy); 161 if (requireSorting) { 162 ArrayList<Long> outPtsListCopy = new ArrayList<>(mOutPtsList); 163 Collections.sort(outPtsListCopy); 164 return arePtsListsIdentical(inpPtsListCopy, outPtsListCopy, mErrorLogs); 165 } 166 return arePtsListsIdentical(inpPtsListCopy, mOutPtsList, mErrorLogs); 167 } 168 getOutStreamSize()169 public int getOutStreamSize() { 170 return mMemIndex; 171 } 172 checksum(ByteBuffer buf, int size)173 public void checksum(ByteBuffer buf, int size) { 174 checksum(buf, size, 0, 0, 0, 0); 175 } 176 checksum(ByteBuffer buf, MediaCodec.BufferInfo info)177 public void checksum(ByteBuffer buf, MediaCodec.BufferInfo info) { 178 int pos = buf.position(); 179 buf.position(info.offset); 180 checksum(buf, info.size, 0, 0, 0, 0); 181 buf.position(pos); 182 } 183 checksum(ByteBuffer buf, int size, int width, int height, int stride, int bytesPerSample)184 public void checksum(ByteBuffer buf, int size, int width, int height, int stride, 185 int bytesPerSample) { 186 int cap = buf.capacity(); 187 assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap, 188 size > 0 && size <= cap); 189 if (buf.hasArray()) { 190 if (width > 0 && height > 0 && stride > 0 && bytesPerSample > 0) { 191 int offset = buf.position() + buf.arrayOffset(); 192 byte[] bb = new byte[width * height * bytesPerSample]; 193 for (int i = 0; i < height; ++i) { 194 System.arraycopy(buf.array(), offset, bb, i * width * bytesPerSample, 195 width * bytesPerSample); 196 offset += stride; 197 } 198 mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample); 199 if (ENABLE_DUMP) { 200 dumpY(bb, 0, width * height * bytesPerSample); 201 } 202 } else { 203 mCrc32UsingBuffer.update(buf.array(), buf.position() + buf.arrayOffset(), size); 204 } 205 } else if (width > 0 && height > 0 && stride > 0 && bytesPerSample > 0) { 206 // Checksum only the Y plane 207 int pos = buf.position(); 208 byte[] bb = new byte[width * height * bytesPerSample]; 209 // we parallelize these copies from non-array buffers because it yields 60% speedup on 210 // 4 core systems. On 4k images, this means 4k frame checksums go from 200 to 80 211 // milliseconds, and this allows some of our 4k video tests to run in 4 minutes, 212 // bringing it under the 10 minutes limit imposed by the test infrastructure. 213 IntStream.range(0, height).parallel().forEach(i -> { 214 int offset = pos + stride * i; 215 // Creating a duplicate as Bytebuffer.position() is not threadsafe and the 216 // duplication does not copy the content. 217 ByteBuffer dup = buf.asReadOnlyBuffer(); 218 dup.position(offset); 219 dup.get(bb, i * width * bytesPerSample, width * bytesPerSample); 220 }); 221 mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample); 222 if (ENABLE_DUMP) { 223 dumpY(bb, 0, width * height * bytesPerSample); 224 } 225 buf.position(pos); 226 } else { 227 int pos = buf.position(); 228 final int rdsize = Math.min(4096, size); 229 byte[] bb = new byte[rdsize]; 230 int chk; 231 for (int i = 0; i < size; i += chk) { 232 chk = Math.min(rdsize, size - i); 233 buf.get(bb, 0, chk); 234 mCrc32UsingBuffer.update(bb, 0, chk); 235 } 236 buf.position(pos); 237 } 238 } 239 checksum(Image image)240 public void checksum(Image image) { 241 int format = image.getFormat(); 242 assertTrue("unexpected image format", 243 format == ImageFormat.YUV_420_888 || format == ImageFormat.YCBCR_P010); 244 int bytesPerSample = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3); // YUV420 245 246 Rect cropRect = image.getCropRect(); 247 int imageWidth = cropRect.width(); 248 int imageHeight = cropRect.height(); 249 assertTrue("unexpected image dimensions", imageWidth > 0 && imageHeight > 0); 250 251 int imageLeft = cropRect.left; 252 int imageTop = cropRect.top; 253 Image.Plane[] planes = image.getPlanes(); 254 for (int i = 0; i < planes.length; ++i) { 255 ByteBuffer buf = planes[i].getBuffer(); 256 int width, height, rowStride, pixelStride, left, top; 257 rowStride = planes[i].getRowStride(); 258 pixelStride = planes[i].getPixelStride(); 259 if (i == 0) { 260 assertEquals(bytesPerSample, pixelStride); 261 width = imageWidth; 262 height = imageHeight; 263 left = imageLeft; 264 top = imageTop; 265 } else { 266 width = imageWidth / 2; 267 height = imageHeight / 2; 268 left = imageLeft / 2; 269 top = imageTop / 2; 270 } 271 int cropOffset = (left * pixelStride) + top * rowStride; 272 // local contiguous pixel buffer 273 byte[] bb = new byte[width * height * bytesPerSample]; 274 275 if (buf.hasArray()) { 276 byte[] b = buf.array(); 277 int offs = buf.arrayOffset() + cropOffset; 278 if (pixelStride == bytesPerSample) { 279 for (int y = 0; y < height; ++y) { 280 System.arraycopy(b, offs + y * rowStride, bb, y * width * bytesPerSample, 281 width * bytesPerSample); 282 } 283 } else { 284 // do it pixel-by-pixel 285 for (int y = 0; y < height; ++y) { 286 int lineOffset = offs + y * rowStride; 287 for (int x = 0; x < width; ++x) { 288 for (int bytePos = 0; bytePos < bytesPerSample; ++bytePos) { 289 bb[y * width * bytesPerSample + x * bytesPerSample + bytePos] = 290 b[lineOffset + x * pixelStride + bytePos]; 291 } 292 } 293 } 294 } 295 } else { // almost always ends up here due to direct buffers 296 int base = buf.position(); 297 int pos = base + cropOffset; 298 // we parallelize these copies from non-array buffers because it yields 60% speedup on 299 // 4 core systems. On 4k images, this means 4k frame checksums go from 200 to 80 300 // milliseconds, and this allows some of our 4k video tests to run in 4 minutes, 301 // bringing it under the 10 minutes limit imposed by the test infrastructure. 302 if (pixelStride == bytesPerSample) { 303 IntStream.range(0, height).parallel().forEach(y -> { 304 // Creating a duplicate as Bytebuffer.position() is not threadsafe and the 305 // duplication does not copy the content. 306 ByteBuffer dup = buf.asReadOnlyBuffer(); 307 dup.position(pos + y * rowStride); 308 dup.get(bb, y * width * bytesPerSample, width * bytesPerSample); 309 }); 310 } else { 311 IntStream.range(0, height).parallel().forEach(y -> { 312 byte[] lb = new byte[rowStride]; 313 // Creating a duplicate as Bytebuffer.position() is not threadsafe and the 314 // duplication does not copy the content. 315 ByteBuffer dup = buf.asReadOnlyBuffer(); 316 dup.position(pos + y * rowStride); 317 // we're only guaranteed to have pixelStride * (width - 1) + 318 // bytesPerSample bytes 319 dup.get(lb, 0, pixelStride * (width - 1) + bytesPerSample); 320 for (int x = 0; x < width; ++x) { 321 for (int bytePos = 0; bytePos < bytesPerSample; ++bytePos) { 322 bb[y * width * bytesPerSample + x * bytesPerSample + bytePos] = 323 lb[x * pixelStride + bytePos]; 324 } 325 } 326 }); 327 } 328 buf.position(base); 329 } 330 mCrc32UsingImage.update(bb, 0, width * height * bytesPerSample); 331 if (ENABLE_DUMP) { 332 dumpYuv(bb, 0, width * height * bytesPerSample); 333 } 334 } 335 } 336 saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info)337 public void saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info) { 338 if (mMemIndex + info.size >= mMemory.length) { 339 mMemory = Arrays.copyOf(mMemory, mMemIndex + info.size); 340 } 341 int base = buf.position(); 342 buf.position(info.offset); 343 buf.get(mMemory, mMemIndex, info.size); 344 mMemIndex += info.size; 345 buf.position(base); 346 } 347 position(int index)348 void position(int index) { 349 if (index < 0 || index >= mMemory.length) index = 0; 350 mMemIndex = index; 351 } 352 getBuffer()353 public ByteBuffer getBuffer() { 354 return ByteBuffer.wrap(mMemory); 355 } 356 getSharedErrorLogs()357 public StringBuilder getSharedErrorLogs() { 358 return mSharedErrorLogs; 359 } 360 reset()361 public void reset() { 362 position(0); 363 mCrc32UsingImage.reset(); 364 mCrc32UsingBuffer.reset(); 365 mInpPtsList.clear(); 366 mOutPtsList.clear(); 367 mSharedErrorLogs.setLength(0); 368 mErrorLogs.setLength(0); 369 mErrorLogs.append("################## Error Details ####################\n"); 370 cleanUp(); 371 } 372 cleanUp()373 public void cleanUp() { 374 if (mOutFileYuv != null && mOutFileYuv.exists()) mOutFileYuv.delete(); 375 mOutFileYuv = null; 376 mAppendToYuvFile = false; 377 if (mOutFileY != null && mOutFileY.exists()) mOutFileY.delete(); 378 mOutFileY = null; 379 mAppendToYFile = false; 380 if (mOutFileDefault != null && mOutFileDefault.exists()) mOutFileDefault.delete(); 381 mOutFileDefault = null; 382 } 383 getRmsError(Object refObject, int audioFormat)384 public float getRmsError(Object refObject, int audioFormat) { 385 double totalErrorSquared = 0; 386 double avgErrorSquared; 387 int bytesPerSample = AudioFormat.getBytesPerSample(audioFormat); 388 if (refObject instanceof float[]) { 389 if (audioFormat != AudioFormat.ENCODING_PCM_FLOAT) return Float.MAX_VALUE; 390 float[] refData = (float[]) refObject; 391 if (refData.length != mMemIndex / bytesPerSample) return Float.MAX_VALUE; 392 float[] floatData = new float[refData.length]; 393 ByteBuffer.wrap(mMemory, 0, mMemIndex).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() 394 .get(floatData); 395 for (int i = 0; i < refData.length; i++) { 396 float d = floatData[i] - refData[i]; 397 totalErrorSquared += d * d; 398 } 399 avgErrorSquared = (totalErrorSquared / refData.length); 400 } else if (refObject instanceof int[]) { 401 int[] refData = (int[]) refObject; 402 int[] intData; 403 if (audioFormat == AudioFormat.ENCODING_PCM_24BIT_PACKED) { 404 if (refData.length != (mMemIndex / bytesPerSample)) return Float.MAX_VALUE; 405 intData = new int[refData.length]; 406 for (int i = 0, j = 0; i < mMemIndex; i += 3, j++) { 407 intData[j] = mMemory[j] | (mMemory[j + 1] << 8) | (mMemory[j + 2] << 16); 408 } 409 } else if (audioFormat == AudioFormat.ENCODING_PCM_32BIT) { 410 if (refData.length != mMemIndex / bytesPerSample) return Float.MAX_VALUE; 411 intData = new int[refData.length]; 412 ByteBuffer.wrap(mMemory, 0, mMemIndex).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer() 413 .get(intData); 414 } else { 415 return Float.MAX_VALUE; 416 } 417 for (int i = 0; i < intData.length; i++) { 418 float d = intData[i] - refData[i]; 419 totalErrorSquared += d * d; 420 } 421 avgErrorSquared = (totalErrorSquared / refData.length); 422 } else if (refObject instanceof short[]) { 423 short[] refData = (short[]) refObject; 424 if (refData.length != mMemIndex / bytesPerSample) return Float.MAX_VALUE; 425 if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) return Float.MAX_VALUE; 426 short[] shortData = new short[refData.length]; 427 ByteBuffer.wrap(mMemory, 0, mMemIndex).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer() 428 .get(shortData); 429 for (int i = 0; i < shortData.length; i++) { 430 float d = shortData[i] - refData[i]; 431 totalErrorSquared += d * d; 432 } 433 avgErrorSquared = (totalErrorSquared / refData.length); 434 } else if (refObject instanceof byte[]) { 435 byte[] refData = (byte[]) refObject; 436 if (refData.length != mMemIndex / bytesPerSample) return Float.MAX_VALUE; 437 if (audioFormat != AudioFormat.ENCODING_PCM_8BIT) return Float.MAX_VALUE; 438 byte[] byteData = new byte[refData.length]; 439 ByteBuffer.wrap(mMemory, 0, mMemIndex).get(byteData); 440 for (int i = 0; i < byteData.length; i++) { 441 float d = byteData[i] - refData[i]; 442 totalErrorSquared += d * d; 443 } 444 avgErrorSquared = (totalErrorSquared / refData.length); 445 } else { 446 return Float.MAX_VALUE; 447 } 448 return (float) Math.sqrt(avgErrorSquared); 449 } 450 getCheckSumImage()451 public long getCheckSumImage() { 452 return mCrc32UsingImage.getValue(); 453 } 454 getCheckSumBuffer()455 public long getCheckSumBuffer() { 456 return mCrc32UsingBuffer.getValue(); 457 } 458 459 @Override equals(Object o)460 public boolean equals(Object o) { 461 if (this == o) return true; 462 if (o == null || getClass() != o.getClass()) return false; 463 if (!this.equalsPtsList(o)) return false; 464 if (!this.equalsDequeuedOutput(o)) return false; 465 return true; 466 } 467 equalsPtsList(Object o)468 public boolean equalsPtsList(Object o) { 469 if (this == o) return true; 470 if (o == null || getClass() != o.getClass()) return false; 471 OutputManager that = (OutputManager) o; 472 return arePtsListsIdentical(mOutPtsList, that.mOutPtsList, mSharedErrorLogs); 473 } 474 equalsByteOutput(Object o)475 public boolean equalsByteOutput(Object o) { 476 if (this == o) return true; 477 if (o == null || getClass() != o.getClass()) return false; 478 OutputManager that = (OutputManager) o; 479 boolean isEqual = true; 480 if (mMemIndex == that.mMemIndex) { 481 if (!Arrays.equals(mMemory, that.mMemory)) { 482 isEqual = false; 483 int count = 0; 484 StringBuilder msg = new StringBuilder(); 485 for (int i = 0; i < mMemIndex; i++) { 486 if (mMemory[i] != that.mMemory[i]) { 487 count++; 488 msg.append(String.format(Locale.getDefault(), 489 "At offset %d, ref buffer val is %x and test buffer val is %x \n", 490 i, mMemory[i], that.mMemory[i])); 491 if (count == 20) { 492 msg.append("stopping after 20 mismatches, ...\n"); 493 break; 494 } 495 } 496 } 497 if (count != 0) { 498 mSharedErrorLogs.append("Ref and Test outputs are not identical \n"); 499 mSharedErrorLogs.append(msg); 500 } 501 } 502 } else { 503 isEqual = false; 504 mSharedErrorLogs.append("ref and test output sizes are not identical \n"); 505 mSharedErrorLogs.append( 506 String.format(Locale.getDefault(), "Ref output buffer size %d \n", mMemIndex)); 507 mSharedErrorLogs.append( 508 String.format(Locale.getDefault(), "Test output buffer size %d \n", 509 that.mMemIndex)); 510 } 511 return isEqual; 512 } 513 514 // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders 515 // produce multiple progressive frames?) For now, do not verify timestamps. equalsDequeuedOutput(Object o)516 public boolean equalsDequeuedOutput(Object o) { 517 if (this == o) return true; 518 if (o == null || getClass() != o.getClass()) return false; 519 OutputManager that = (OutputManager) o; 520 boolean isEqual = true; 521 if (mCrc32UsingImage.getValue() != that.mCrc32UsingImage.getValue()) { 522 isEqual = false; 523 mSharedErrorLogs.append("CRC32 checksums computed for image buffers received from " 524 + "getOutputImage() do not match between ref and test runs. \n"); 525 mSharedErrorLogs.append(String.format(Locale.getDefault(), 526 "Ref CRC32 checksum value is %d \n", mCrc32UsingImage.getValue())); 527 mSharedErrorLogs.append(String.format(Locale.getDefault(), 528 "Test CRC32 checksum value is %d \n", that.mCrc32UsingImage.getValue())); 529 if (ENABLE_DUMP) { 530 mSharedErrorLogs.append(String.format(Locale.getDefault(), 531 "Decoded Ref YUV file is at : %s \n", mOutFileYuv.getAbsolutePath())); 532 mSharedErrorLogs.append(String.format(Locale.getDefault(), 533 "Decoded Test YUV file is at : %s \n", that.mOutFileYuv.getAbsolutePath())); 534 } else { 535 mSharedErrorLogs.append("As the reference YUV and test YUV are different, try " 536 + "re-running the test by changing ENABLE_DUMP of OutputManager class to " 537 + "'true' to dump the decoded YUVs for further analysis. \n"); 538 } 539 } 540 if (mCrc32UsingBuffer.getValue() != that.mCrc32UsingBuffer.getValue()) { 541 isEqual = false; 542 mSharedErrorLogs.append("CRC32 checksums computed for byte buffers received from " 543 + "getOutputBuffer() do not match between ref and test runs. \n"); 544 mSharedErrorLogs.append(String.format(Locale.getDefault(), 545 "Ref CRC32 checksum value is %d \n", mCrc32UsingBuffer.getValue())); 546 mSharedErrorLogs.append(String.format(Locale.getDefault(), 547 "Test CRC32 checksum value is %d \n", that.mCrc32UsingBuffer.getValue())); 548 if (ENABLE_DUMP) { 549 if (mOutFileY != null) { 550 mSharedErrorLogs.append(String.format(Locale.getDefault(), 551 "Decoded Ref Y file is at : %s \n", mOutFileY.getAbsolutePath())); 552 } 553 if (that.mOutFileY != null) { 554 mSharedErrorLogs.append(String.format(Locale.getDefault(), 555 "Decoded Test Y file is at : %s \n", that.mOutFileY.getAbsolutePath())); 556 } 557 if (mMemIndex > 0) { 558 mSharedErrorLogs.append(String.format(Locale.getDefault(), 559 "Output Ref ByteBuffer is dumped at : %s \n", dumpBuffer())); 560 } 561 if (that.mMemIndex > 0) { 562 mSharedErrorLogs.append(String.format(Locale.getDefault(), 563 "Output Test ByteBuffer is dumped at : %s \n", that.dumpBuffer())); 564 } 565 } else { 566 mSharedErrorLogs.append("As the output of the component is not consistent, try " 567 + "re-running the test by changing ENABLE_DUMP of OutputManager class to " 568 + "'true' to dump the outputs for further analysis. \n"); 569 } 570 } 571 if (!equalsByteOutput(that)) { 572 isEqual = false; 573 } 574 return isEqual; 575 } 576 getErrMsg()577 public String getErrMsg() { 578 return (mErrorLogs.toString() + mSharedErrorLogs.toString()); 579 } 580 dumpYuv(byte[] mem, int offset, int size)581 public void dumpYuv(byte[] mem, int offset, int size) { 582 try { 583 if (mOutFileYuv == null) { 584 mOutFileYuv = File.createTempFile(LOG_TAG + "YUV", ".bin"); 585 mOutFileYuv.deleteOnExit(); 586 } 587 try (FileOutputStream outputStream = new FileOutputStream(mOutFileYuv, 588 mAppendToYuvFile)) { 589 outputStream.write(mem, offset, size); 590 mAppendToYuvFile = true; 591 } 592 } catch (Exception e) { 593 fail("Encountered IOException during output image write. Exception is" + e); 594 } 595 } 596 dumpY(byte[] mem, int offset, int size)597 public void dumpY(byte[] mem, int offset, int size) { 598 try { 599 if (mOutFileY == null) { 600 mOutFileY = File.createTempFile(LOG_TAG + "Y", ".bin"); 601 mOutFileY.deleteOnExit(); 602 } 603 try (FileOutputStream outputStream = new FileOutputStream(mOutFileY, mAppendToYFile)) { 604 outputStream.write(mem, offset, size); 605 mAppendToYFile = true; 606 } 607 } catch (Exception e) { 608 fail("Encountered IOException during output image write. Exception is" + e); 609 } 610 } 611 dumpBuffer()612 public String dumpBuffer() { 613 if (ENABLE_DUMP) { 614 try { 615 if (mOutFileDefault == null) { 616 mOutFileDefault = File.createTempFile(LOG_TAG + "OUT", ".bin"); 617 mOutFileDefault.deleteOnExit(); 618 } 619 try (FileOutputStream outputStream = new FileOutputStream(mOutFileDefault)) { 620 outputStream.write(mMemory, 0, mMemIndex); 621 } 622 } catch (Exception e) { 623 fail("Encountered IOException during output buffer write. Exception is" + e); 624 } 625 return mOutFileDefault.getAbsolutePath(); 626 } 627 return "file not dumped yet, re-run the test by changing ENABLE_DUMP of OutputManager " 628 + "class to 'true' to dump the buffer"; 629 } 630 getOutYuvFileName()631 public String getOutYuvFileName() { 632 return (mOutFileYuv != null) ? mOutFileYuv.getAbsolutePath() : null; 633 } 634 } 635