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