mediav2 CTS: Improve failure diagnostic messages - 3

Add provision to dump output of component under test if possible.

Bug: 220100512
Test: atest android.mediav2.cts

Change-Id: Ic68ade5489bae92c1e92e47b6d274fa7ce6e27ff
diff --git a/tests/media/common/src/android/mediav2/common/cts/OutputManager.java b/tests/media/common/src/android/mediav2/common/cts/OutputManager.java
index 5d879c65..d59f432 100644
--- a/tests/media/common/src/android/mediav2/common/cts/OutputManager.java
+++ b/tests/media/common/src/android/mediav2/common/cts/OutputManager.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
@@ -25,6 +26,8 @@
 import android.media.Image;
 import android.media.MediaCodec;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
@@ -38,9 +41,17 @@
  * in memory and outPtsList fields of this class. For video decoders, the decoded information can
  * be overwhelming as it is uncompressed YUV. For them we compute the CRC32 checksum of the
  * output image and buffer and store it instead.
+ *
+ * ByteBuffer output of encoder/decoder components can be written to disk by setting ENABLE_DUMP
+ * to true. Exercise CAUTION while running tests with ENABLE_DUMP set to true as this will crowd
+ * the storage with files. These files are configured to be deleted on exit. So, in order to see
+ * the captured output, File.deleteOnExit() needs to be be commented. Also it might be necessary
+ * to set option name="cleanup-apks" to "false" in AndroidTest.xml.
  */
 public class OutputManager {
     private static final String LOG_TAG = OutputManager.class.getSimpleName();
+    private static final boolean ENABLE_DUMP = false;
+
     private byte[] mMemory;
     private int mMemIndex;
     private final CRC32 mCrc32UsingImage;
@@ -49,6 +60,11 @@
     private final ArrayList<Long> mOutPtsList;
     private final StringBuilder mErrorLogs;
     private final StringBuilder mSharedErrorLogs;
+    private File mOutFileYuv;
+    private boolean mAppendToYuvFile;
+    private File mOutFileY;
+    private boolean mAppendToYFile;
+    private File mOutFileDefault;
 
     public OutputManager() {
         this(new StringBuilder());
@@ -170,6 +186,9 @@
                     offset += stride;
                 }
                 mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample);
+                if (ENABLE_DUMP) {
+                    dumpY(bb, 0, width * height * bytesPerSample);
+                }
             } else {
                 mCrc32UsingBuffer.update(buf.array(), buf.position() + buf.arrayOffset(), size);
             }
@@ -184,6 +203,9 @@
                 offset += stride;
             }
             mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample);
+            if (ENABLE_DUMP) {
+                dumpY(bb, 0, width * height * bytesPerSample);
+            }
             buf.position(pos);
         } else {
             int pos = buf.position();
@@ -282,6 +304,9 @@
                 buf.position(base);
             }
             mCrc32UsingImage.update(bb, 0, width * height * bytesPerSample);
+            if (ENABLE_DUMP) {
+                dumpYuv(bb, 0, width * height * bytesPerSample);
+            }
         }
     }
 
@@ -316,6 +341,18 @@
         mSharedErrorLogs.setLength(0);
         mErrorLogs.setLength(0);
         mErrorLogs.append("##################       Error Details         ####################\n");
+        cleanUp();
+    }
+
+    public void cleanUp() {
+        if (mOutFileYuv != null && mOutFileYuv.exists()) mOutFileYuv.delete();
+        mOutFileYuv = null;
+        mAppendToYuvFile = false;
+        if (mOutFileY != null && mOutFileY.exists()) mOutFileY.delete();
+        mOutFileY = null;
+        mAppendToYFile = false;
+        if (mOutFileDefault != null && mOutFileDefault.exists()) mOutFileDefault.delete();
+        mOutFileDefault = null;
     }
 
     public float getRmsError(Object refObject, int audioFormat) {
@@ -418,6 +455,16 @@
                     mCrc32UsingImage.getValue()));
             mSharedErrorLogs.append(String.format("Test CRC32 checksum value is %d \n",
                     that.mCrc32UsingImage.getValue()));
+            if (ENABLE_DUMP) {
+                mSharedErrorLogs.append(String.format("Decoded Ref YUV file is at : %s \n",
+                        mOutFileYuv.getAbsolutePath()));
+                mSharedErrorLogs.append(String.format("Decoded Test YUV file is at : %s \n",
+                        that.mOutFileYuv.getAbsolutePath()));
+            } else {
+                mSharedErrorLogs.append("As the reference YUV and test YUV are different, try "
+                        + "re-running the test by changing ENABLE_DUMP of OutputManager class to "
+                        + "'true' to dump the decoded YUVs for further analysis. \n");
+            }
         }
         if (mCrc32UsingBuffer.getValue() != that.mCrc32UsingBuffer.getValue()) {
             isEqual = false;
@@ -427,6 +474,30 @@
                     mCrc32UsingBuffer.getValue()));
             mSharedErrorLogs.append(String.format("Test CRC32 checksum value is %d \n",
                     that.mCrc32UsingBuffer.getValue()));
+            if (ENABLE_DUMP) {
+                if (mOutFileY != null) {
+                    mSharedErrorLogs.append(String.format("Decoded Ref Y file is at : %s \n",
+                            mOutFileY.getAbsolutePath()));
+                }
+                if (that.mOutFileY != null) {
+                    mSharedErrorLogs.append(String.format("Decoded Test Y file is at : %s \n",
+                            that.mOutFileY.getAbsolutePath()));
+                }
+                if (mMemIndex > 0) {
+                    mSharedErrorLogs.append(
+                            String.format("Output Ref ByteBuffer is dumped at : %s \n",
+                                    dumpBuffer()));
+                }
+                if (that.mMemIndex > 0) {
+                    mSharedErrorLogs.append(
+                            String.format("Output Test ByteBuffer is dumped at : %s \n",
+                                    that.dumpBuffer()));
+                }
+            } else {
+                mSharedErrorLogs.append("As the output of the component is not consistent, try "
+                        + "re-running the test by changing ENABLE_DUMP of OutputManager class to "
+                        + "'true' to dump the outputs for further analysis. \n");
+            }
             if (mMemIndex == that.mMemIndex) {
                 int count = 0;
                 StringBuilder msg = new StringBuilder();
@@ -459,4 +530,58 @@
     public String getErrMsg() {
         return (mErrorLogs.toString() + mSharedErrorLogs.toString());
     }
+
+    public void dumpYuv(byte[] mem, int offset, int size) {
+        try {
+            if (mOutFileYuv == null) {
+                mOutFileYuv = File.createTempFile(LOG_TAG + "YUV", ".bin");
+                mOutFileYuv.deleteOnExit();
+            }
+            try (FileOutputStream outputStream = new FileOutputStream(mOutFileYuv,
+                    mAppendToYuvFile)) {
+                outputStream.write(mem, offset, size);
+                mAppendToYuvFile = true;
+            }
+        } catch (Exception e) {
+            fail("Encountered IOException during output image write. Exception is" + e);
+        }
+    }
+
+    public void dumpY(byte[] mem, int offset, int size) {
+        try {
+            if (mOutFileY == null) {
+                mOutFileY = File.createTempFile(LOG_TAG + "Y", ".bin");
+                mOutFileY.deleteOnExit();
+            }
+            try (FileOutputStream outputStream = new FileOutputStream(mOutFileY, mAppendToYFile)) {
+                outputStream.write(mem, offset, size);
+                mAppendToYFile = true;
+            }
+        } catch (Exception e) {
+            fail("Encountered IOException during output image write. Exception is" + e);
+        }
+    }
+
+    public String dumpBuffer() {
+        if (ENABLE_DUMP) {
+            try {
+                if (mOutFileDefault == null) {
+                    mOutFileDefault = File.createTempFile(LOG_TAG + "OUT", ".bin");
+                    mOutFileDefault.deleteOnExit();
+                }
+                try (FileOutputStream outputStream = new FileOutputStream(mOutFileDefault)) {
+                    outputStream.write(mMemory, 0, mMemIndex);
+                }
+            } catch (Exception e) {
+                fail("Encountered IOException during output buffer write. Exception is" + e);
+            }
+            return mOutFileDefault.getAbsolutePath();
+        }
+        return "file not dumped yet, re-run the test by changing ENABLE_DUMP of OutputManager "
+                + "class to 'true' to dump the buffer";
+    }
+
+    public String getOutYuvFileName() {
+        return (mOutFileYuv != null) ? mOutFileYuv.getAbsolutePath() : null;
+    }
 }