• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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