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