Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright 2014 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.media.cts;
     18 
     19 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
     20 
     21 import android.content.Context;
     22 import android.content.res.AssetFileDescriptor;
     23 import android.content.res.Resources;
     24 import android.content.res.Resources.NotFoundException;
     25 import android.graphics.ImageFormat;
     26 import android.graphics.Rect;
     27 import android.media.Image;
     28 import android.media.Image.Plane;
     29 import android.media.ImageReader;
     30 import android.media.MediaCodec;
     31 import android.media.MediaCodecInfo;
     32 import android.media.MediaCodecInfo.CodecCapabilities;
     33 import android.media.MediaCodecInfo.VideoCapabilities;
     34 import android.media.MediaCodecList;
     35 import android.media.MediaExtractor;
     36 import android.media.MediaFormat;
     37 import android.media.cts.CodecUtils;
     38 import android.media.cts.R;
     39 import android.os.Handler;
     40 import android.os.HandlerThread;
     41 import android.platform.test.annotations.RequiresDevice;
     42 import android.test.AndroidTestCase;
     43 import android.util.Log;
     44 import android.view.Surface;
     45 
     46 import androidx.test.filters.SmallTest;
     47 
     48 import com.android.compatibility.common.util.MediaUtils;
     49 
     50 import java.io.File;
     51 import java.io.FileOutputStream;
     52 import java.io.IOException;
     53 import java.io.InputStream;
     54 import java.nio.ByteBuffer;
     55 import java.util.ArrayList;
     56 import java.util.Arrays;
     57 import java.util.concurrent.LinkedBlockingQueue;
     58 import java.util.concurrent.TimeUnit;
     59 
     60 /**
     61  * Basic test for ImageReader APIs.
     62  * <p>
     63  * It uses MediaCodec to decode a short video stream, send the video frames to
     64  * the surface provided by ImageReader. Then compare if output buffers of the
     65  * ImageReader matches the output buffers of the MediaCodec. The video format
     66  * used here is AVC although the compression format doesn't matter for this
     67  * test. For decoder test, hw and sw decoders are tested,
     68  * </p>
     69  */
     70 @SmallTest
     71 @RequiresDevice
     72 public class ImageReaderDecoderTest extends AndroidTestCase {
     73     private static final String TAG = "ImageReaderDecoderTest";
     74     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     75     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     76     private static final long DEFAULT_TIMEOUT_US = 10000;
     77     private static final long WAIT_FOR_IMAGE_TIMEOUT_MS = 1000;
     78     private static final String DEBUG_FILE_NAME_BASE = "/sdcard/";
     79     private static final int NUM_FRAME_DECODED = 100;
     80     // video decoders only support a single outstanding image with the consumer
     81     private static final int MAX_NUM_IMAGES = 1;
     82     private static final float COLOR_STDEV_ALLOWANCE = 5f;
     83     private static final float COLOR_DELTA_ALLOWANCE = 5f;
     84 
     85     private final static int MODE_IMAGEREADER = 0;
     86     private final static int MODE_IMAGE       = 1;
     87 
     88     private Resources mResources;
     89     private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
     90     private ImageReader mReader;
     91     private Surface mReaderSurface;
     92     private HandlerThread mHandlerThread;
     93     private Handler mHandler;
     94     private ImageListener mImageListener;
     95 
     96     @Override
     97     public void setContext(Context context) {
     98         super.setContext(context);
     99         mResources = mContext.getResources();
    100     }
    101 
    102     @Override
    103     protected void setUp() throws Exception {
    104         super.setUp();
    105         mHandlerThread = new HandlerThread(TAG);
    106         mHandlerThread.start();
    107         mHandler = new Handler(mHandlerThread.getLooper());
    108         mImageListener = new ImageListener();
    109     }
    110 
    111     @Override
    112     protected void tearDown() throws Exception {
    113         mHandlerThread.quitSafely();
    114         mHandler = null;
    115     }
    116 
    117     static class MediaAsset {
    118         public MediaAsset(int resource, int width, int height) {
    119             mResource = resource;
    120             mWidth = width;
    121             mHeight = height;
    122         }
    123 
    124         public int getWidth() {
    125             return mWidth;
    126         }
    127 
    128         public int getHeight() {
    129             return mHeight;
    130         }
    131 
    132         public int getResource() {
    133             return mResource;
    134         }
    135 
    136         private final int mResource;
    137         private final int mWidth;
    138         private final int mHeight;
    139     }
    140 
    141     static class MediaAssets {
    142         public MediaAssets(String mime, MediaAsset... assets) {
    143             mMime = mime;
    144             mAssets = assets;
    145         }
    146 
    147         public String getMime() {
    148             return mMime;
    149         }
    150 
    151         public MediaAsset[] getAssets() {
    152             return mAssets;
    153         }
    154 
    155         private final String mMime;
    156         private final MediaAsset[] mAssets;
    157     }
    158 
    159     private static MediaAssets H263_ASSETS = new MediaAssets(
    160             MediaFormat.MIMETYPE_VIDEO_H263,
    161             new MediaAsset(R.raw.swirl_176x144_h263, 176, 144),
    162             new MediaAsset(R.raw.swirl_352x288_h263, 352, 288),
    163             new MediaAsset(R.raw.swirl_128x96_h263, 128, 96));
    164 
    165     private static MediaAssets MPEG4_ASSETS = new MediaAssets(
    166             MediaFormat.MIMETYPE_VIDEO_MPEG4,
    167             new MediaAsset(R.raw.swirl_128x128_mpeg4, 128, 128),
    168             new MediaAsset(R.raw.swirl_144x136_mpeg4, 144, 136),
    169             new MediaAsset(R.raw.swirl_136x144_mpeg4, 136, 144),
    170             new MediaAsset(R.raw.swirl_132x130_mpeg4, 132, 130),
    171             new MediaAsset(R.raw.swirl_130x132_mpeg4, 130, 132));
    172 
    173     private static MediaAssets H264_ASSETS = new MediaAssets(
    174             MediaFormat.MIMETYPE_VIDEO_AVC,
    175             new MediaAsset(R.raw.swirl_128x128_h264, 128, 128),
    176             new MediaAsset(R.raw.swirl_144x136_h264, 144, 136),
    177             new MediaAsset(R.raw.swirl_136x144_h264, 136, 144),
    178             new MediaAsset(R.raw.swirl_132x130_h264, 132, 130),
    179             new MediaAsset(R.raw.swirl_130x132_h264, 130, 132));
    180 
    181     private static MediaAssets H265_ASSETS = new MediaAssets(
    182             MediaFormat.MIMETYPE_VIDEO_HEVC,
    183             new MediaAsset(R.raw.swirl_128x128_h265, 128, 128),
    184             new MediaAsset(R.raw.swirl_144x136_h265, 144, 136),
    185             new MediaAsset(R.raw.swirl_136x144_h265, 136, 144),
    186             new MediaAsset(R.raw.swirl_132x130_h265, 132, 130),
    187             new MediaAsset(R.raw.swirl_130x132_h265, 130, 132));
    188 
    189     private static MediaAssets VP8_ASSETS = new MediaAssets(
    190             MediaFormat.MIMETYPE_VIDEO_VP8,
    191             new MediaAsset(R.raw.swirl_128x128_vp8, 128, 128),
    192             new MediaAsset(R.raw.swirl_144x136_vp8, 144, 136),
    193             new MediaAsset(R.raw.swirl_136x144_vp8, 136, 144),
    194             new MediaAsset(R.raw.swirl_132x130_vp8, 132, 130),
    195             new MediaAsset(R.raw.swirl_130x132_vp8, 130, 132));
    196 
    197     private static MediaAssets VP9_ASSETS = new MediaAssets(
    198             MediaFormat.MIMETYPE_VIDEO_VP9,
    199             new MediaAsset(R.raw.swirl_128x128_vp9, 128, 128),
    200             new MediaAsset(R.raw.swirl_144x136_vp9, 144, 136),
    201             new MediaAsset(R.raw.swirl_136x144_vp9, 136, 144),
    202             new MediaAsset(R.raw.swirl_132x130_vp9, 132, 130),
    203             new MediaAsset(R.raw.swirl_130x132_vp9, 130, 132));
    204 
    205     static final float SWIRL_FPS = 12.f;
    206 
    207     class Decoder {
    208         final private String mName;
    209         final private String mMime;
    210         final private VideoCapabilities mCaps;
    211         final private ArrayList<MediaAsset> mAssets;
    212 
    213         boolean isFlexibleFormatSupported(CodecCapabilities caps) {
    214             for (int c : caps.colorFormats) {
    215                 if (c == COLOR_FormatYUV420Flexible) {
    216                     return true;
    217                 }
    218             }
    219             return false;
    220         }
    221 
    222         Decoder(String name, MediaAssets assets, CodecCapabilities caps) {
    223             mName = name;
    224             mMime = assets.getMime();
    225             mCaps = caps.getVideoCapabilities();
    226             mAssets = new ArrayList<MediaAsset>();
    227 
    228             for (MediaAsset asset : assets.getAssets()) {
    229                 if (mCaps.areSizeAndRateSupported(asset.getWidth(), asset.getHeight(), SWIRL_FPS)
    230                         && isFlexibleFormatSupported(caps)) {
    231                     mAssets.add(asset);
    232                 }
    233             }
    234         }
    235 
    236         public boolean videoDecode(int mode, boolean checkSwirl) {
    237             boolean skipped = true;
    238             for (MediaAsset asset: mAssets) {
    239                 // TODO: loop over all supported image formats
    240                 int imageFormat = ImageFormat.YUV_420_888;
    241                 int colorFormat = COLOR_FormatYUV420Flexible;
    242                 videoDecode(asset, imageFormat, colorFormat, mode, checkSwirl);
    243                 skipped = false;
    244             }
    245             return skipped;
    246         }
    247 
    248         private void videoDecode(
    249                 MediaAsset asset, int imageFormat, int colorFormat, int mode, boolean checkSwirl) {
    250             int video = asset.getResource();
    251             int width = asset.getWidth();
    252             int height = asset.getHeight();
    253 
    254             if (DEBUG) Log.d(TAG, "videoDecode " + mName + " " + width + "x" + height);
    255 
    256             MediaCodec decoder = null;
    257             AssetFileDescriptor vidFD = null;
    258 
    259             MediaExtractor extractor = null;
    260             File tmpFile = null;
    261             InputStream is = null;
    262             FileOutputStream os = null;
    263             MediaFormat mediaFormat = null;
    264             try {
    265                 extractor = new MediaExtractor();
    266 
    267                 try {
    268                     vidFD = mResources.openRawResourceFd(video);
    269                     extractor.setDataSource(
    270                             vidFD.getFileDescriptor(), vidFD.getStartOffset(), vidFD.getLength());
    271                 } catch (NotFoundException e) {
    272                     // resource is compressed, uncompress locally
    273                     String tmpName = "tempStream";
    274                     tmpFile = File.createTempFile(tmpName, null, mContext.getCacheDir());
    275                     is = mResources.openRawResource(video);
    276                     os = new FileOutputStream(tmpFile);
    277                     byte[] buf = new byte[1024];
    278                     int len;
    279                     while ((len = is.read(buf, 0, buf.length)) > 0) {
    280                         os.write(buf, 0, len);
    281                     }
    282                     os.close();
    283                     is.close();
    284 
    285                     extractor.setDataSource(tmpFile.getAbsolutePath());
    286                 }
    287 
    288                 mediaFormat = extractor.getTrackFormat(0);
    289                 mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
    290 
    291                 // Create decoder
    292                 decoder = MediaCodec.createByCodecName(mName);
    293                 assertNotNull("couldn't create decoder" + mName, decoder);
    294 
    295                 decodeFramesToImage(
    296                         decoder, extractor, mediaFormat,
    297                         width, height, imageFormat, mode, checkSwirl);
    298 
    299                 decoder.stop();
    300                 if (vidFD != null) {
    301                     vidFD.close();
    302                 }
    303             } catch (Throwable e) {
    304                 throw new RuntimeException("while " + mName + " decoding "
    305                         + mResources.getResourceEntryName(video) + ": " + mediaFormat, e);
    306             } finally {
    307                 if (decoder != null) {
    308                     decoder.release();
    309                 }
    310                 if (extractor != null) {
    311                     extractor.release();
    312                 }
    313                 if (tmpFile != null) {
    314                     tmpFile.delete();
    315                 }
    316             }
    317         }
    318     }
    319 
    320     private Decoder[] decoders(MediaAssets assets, boolean goog) {
    321         String mime = assets.getMime();
    322         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    323         ArrayList<Decoder> result = new ArrayList<Decoder>();
    324 
    325         for (MediaCodecInfo info : mcl.getCodecInfos()) {
    326             if (info.isEncoder() || info.isAlias() || !info.isVendor() != goog) {
    327                 continue;
    328             }
    329             CodecCapabilities caps = null;
    330             try {
    331                 caps = info.getCapabilitiesForType(mime);
    332             } catch (IllegalArgumentException e) { // mime is not supported
    333                 continue;
    334             }
    335             assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
    336             result.add(new Decoder(info.getName(), assets, caps));
    337         }
    338         return result.toArray(new Decoder[result.size()]);
    339     }
    340 
    341     private Decoder[] goog(MediaAssets assets) {
    342         return decoders(assets, true /* goog */);
    343     }
    344 
    345     private Decoder[] other(MediaAssets assets) {
    346         return decoders(assets, false /* goog */);
    347     }
    348 
    349     private Decoder[] googH265()  { return goog(H265_ASSETS); }
    350     private Decoder[] googH264()  { return goog(H264_ASSETS); }
    351     private Decoder[] googH263()  { return goog(H263_ASSETS); }
    352     private Decoder[] googMpeg4() { return goog(MPEG4_ASSETS); }
    353     private Decoder[] googVP8()   { return goog(VP8_ASSETS); }
    354     private Decoder[] googVP9()   { return goog(VP9_ASSETS); }
    355 
    356     private Decoder[] otherH265()  { return other(H265_ASSETS); }
    357     private Decoder[] otherH264()  { return other(H264_ASSETS); }
    358     private Decoder[] otherH263()  { return other(H263_ASSETS); }
    359     private Decoder[] otherMpeg4() { return other(MPEG4_ASSETS); }
    360     private Decoder[] otherVP8()   { return other(VP8_ASSETS); }
    361     private Decoder[] otherVP9()   { return other(VP9_ASSETS); }
    362 
    363     public void testGoogH265Image()   { swirlTest(googH265(),   MODE_IMAGE); }
    364     public void testGoogH264Image()   { swirlTest(googH264(),   MODE_IMAGE); }
    365     public void testGoogH263Image()   { swirlTest(googH263(),   MODE_IMAGE); }
    366     public void testGoogMpeg4Image()  { swirlTest(googMpeg4(),  MODE_IMAGE); }
    367     public void testGoogVP8Image()    { swirlTest(googVP8(),    MODE_IMAGE); }
    368     public void testGoogVP9Image()    { swirlTest(googVP9(),    MODE_IMAGE); }
    369 
    370     public void testOtherH265Image()  { swirlTest(otherH265(),  MODE_IMAGE); }
    371     public void testOtherH264Image()  { swirlTest(otherH264(),  MODE_IMAGE); }
    372     public void testOtherH263Image()  { swirlTest(otherH263(),  MODE_IMAGE); }
    373     public void testOtherMpeg4Image() { swirlTest(otherMpeg4(), MODE_IMAGE); }
    374     public void testOtherVP8Image()   { swirlTest(otherVP8(),   MODE_IMAGE); }
    375     public void testOtherVP9Image()   { swirlTest(otherVP9(),   MODE_IMAGE); }
    376 
    377     public void testGoogH265ImageReader()   { swirlTest(googH265(),   MODE_IMAGEREADER); }
    378     public void testGoogH264ImageReader()   { swirlTest(googH264(),   MODE_IMAGEREADER); }
    379     public void testGoogH263ImageReader()   { swirlTest(googH263(),   MODE_IMAGEREADER); }
    380     public void testGoogMpeg4ImageReader()  { swirlTest(googMpeg4(),  MODE_IMAGEREADER); }
    381     public void testGoogVP8ImageReader()    { swirlTest(googVP8(),    MODE_IMAGEREADER); }
    382     public void testGoogVP9ImageReader()    { swirlTest(googVP9(),    MODE_IMAGEREADER); }
    383 
    384     public void testOtherH265ImageReader()  { swirlTest(otherH265(),  MODE_IMAGEREADER); }
    385     public void testOtherH264ImageReader()  { swirlTest(otherH264(),  MODE_IMAGEREADER); }
    386     public void testOtherH263ImageReader()  { swirlTest(otherH263(),  MODE_IMAGEREADER); }
    387     public void testOtherMpeg4ImageReader() { swirlTest(otherMpeg4(), MODE_IMAGEREADER); }
    388     public void testOtherVP8ImageReader()   { swirlTest(otherVP8(),   MODE_IMAGEREADER); }
    389     public void testOtherVP9ImageReader()   { swirlTest(otherVP9(),   MODE_IMAGEREADER); }
    390 
    391     /**
    392      * Test ImageReader with 480x360 non-google AVC decoding for flexible yuv format
    393      */
    394     public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
    395         Decoder[] decoders = other(new MediaAssets(
    396                 MediaFormat.MIMETYPE_VIDEO_AVC,
    397                 new MediaAsset(
    398                         R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
    399                         480 /* width */, 360 /* height */)));
    400 
    401         decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
    402     }
    403 
    404     /**
    405      * Test ImageReader with 480x360 google (SW) AVC decoding for flexible yuv format
    406      */
    407     public void testSwAVCDecode360pForFlexibleYuv() throws Exception {
    408         Decoder[] decoders = goog(new MediaAssets(
    409                 MediaFormat.MIMETYPE_VIDEO_AVC,
    410                 new MediaAsset(
    411                         R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
    412                         480 /* width */, 360 /* height */)));
    413 
    414         decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
    415     }
    416 
    417     private void swirlTest(Decoder[] decoders, int mode) {
    418         decodeTest(decoders, mode, true /* checkSwirl */);
    419     }
    420 
    421     private void decodeTest(Decoder[] decoders, int mode, boolean checkSwirl) {
    422         try {
    423             boolean skipped = true;
    424             for (Decoder codec : decoders) {
    425                 if (codec.videoDecode(mode, checkSwirl)) {
    426                     skipped = false;
    427                 }
    428             }
    429             if (skipped) {
    430                 MediaUtils.skipTest("decoder does not any of the input files");
    431             }
    432         } finally {
    433             closeImageReader();
    434         }
    435     }
    436 
    437     private static class ImageListener implements ImageReader.OnImageAvailableListener {
    438         private final LinkedBlockingQueue<Image> mQueue =
    439                 new LinkedBlockingQueue<Image>();
    440 
    441         @Override
    442         public void onImageAvailable(ImageReader reader) {
    443             try {
    444                 mQueue.put(reader.acquireNextImage());
    445             } catch (InterruptedException e) {
    446                 throw new UnsupportedOperationException(
    447                         "Can't handle InterruptedException in onImageAvailable");
    448             }
    449         }
    450 
    451         /**
    452          * Get an image from the image reader.
    453          *
    454          * @param timeout Timeout value for the wait.
    455          * @return The image from the image reader.
    456          */
    457         public Image getImage(long timeout) throws InterruptedException {
    458             Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
    459             assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
    460             return image;
    461         }
    462     }
    463 
    464     /**
    465      * Decode video frames to image reader.
    466      */
    467     private void decodeFramesToImage(
    468             MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat,
    469             int width, int height, int imageFormat, int mode, boolean checkSwirl)
    470             throws InterruptedException {
    471         ByteBuffer[] decoderInputBuffers;
    472         ByteBuffer[] decoderOutputBuffers;
    473 
    474         // Configure decoder.
    475         if (VERBOSE) Log.v(TAG, "stream format: " + mediaFormat);
    476         if (mode == MODE_IMAGEREADER) {
    477             createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
    478             decoder.configure(mediaFormat, mReaderSurface, null /* crypto */, 0 /* flags */);
    479         } else {
    480             assertEquals(mode, MODE_IMAGE);
    481             decoder.configure(mediaFormat, null /* surface */, null /* crypto */, 0 /* flags */);
    482         }
    483 
    484         decoder.start();
    485         decoderInputBuffers = decoder.getInputBuffers();
    486         decoderOutputBuffers = decoder.getOutputBuffers();
    487         extractor.selectTrack(0);
    488 
    489         // Start decoding and get Image, only test the first NUM_FRAME_DECODED frames.
    490         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    491         boolean sawInputEOS = false;
    492         boolean sawOutputEOS = false;
    493         int outputFrameCount = 0;
    494         while (!sawOutputEOS && outputFrameCount < NUM_FRAME_DECODED) {
    495             if (VERBOSE) Log.v(TAG, "loop:" + outputFrameCount);
    496             // Feed input frame.
    497             if (!sawInputEOS) {
    498                 int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
    499                 if (inputBufIndex >= 0) {
    500                     ByteBuffer dstBuf = decoderInputBuffers[inputBufIndex];
    501                     int sampleSize =
    502                         extractor.readSampleData(dstBuf, 0 /* offset */);
    503 
    504                     if (VERBOSE) Log.v(TAG, "queue a input buffer, idx/size: "
    505                         + inputBufIndex + "/" + sampleSize);
    506 
    507                     long presentationTimeUs = 0;
    508 
    509                     if (sampleSize < 0) {
    510                         if (VERBOSE) Log.v(TAG, "saw input EOS.");
    511                         sawInputEOS = true;
    512                         sampleSize = 0;
    513                     } else {
    514                         presentationTimeUs = extractor.getSampleTime();
    515                     }
    516 
    517                     decoder.queueInputBuffer(
    518                             inputBufIndex,
    519                             0 /* offset */,
    520                             sampleSize,
    521                             presentationTimeUs,
    522                             sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
    523 
    524                     if (!sawInputEOS) {
    525                         extractor.advance();
    526                     }
    527                 }
    528             }
    529 
    530             // Get output frame
    531             int res = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
    532             if (VERBOSE) Log.v(TAG, "got a buffer: " + info.size + "/" + res);
    533             if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
    534                 // no output available yet
    535                 if (VERBOSE) Log.v(TAG, "no output frame available");
    536             } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    537                 // decoder output buffers changed, need update.
    538                 if (VERBOSE) Log.v(TAG, "decoder output buffers changed");
    539                 decoderOutputBuffers = decoder.getOutputBuffers();
    540             } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    541                 // this happens before the first frame is returned.
    542                 MediaFormat outFormat = decoder.getOutputFormat();
    543                 if (VERBOSE) Log.v(TAG, "decoder output format changed: " + outFormat);
    544             } else if (res < 0) {
    545                 // Should be decoding error.
    546                 fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
    547             } else {
    548                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
    549                     sawOutputEOS = true;
    550                 }
    551 
    552                 // res >= 0: normal decoding case, copy the output buffer.
    553                 // Will use it as reference to valid the ImageReader output
    554                 // Some decoders output a 0-sized buffer at the end. Ignore those.
    555                 boolean doRender = (info.size != 0);
    556 
    557                 if (doRender) {
    558                     outputFrameCount++;
    559                     String fileName = DEBUG_FILE_NAME_BASE + MediaUtils.getTestName()
    560                             + (mode == MODE_IMAGE ? "_image_" : "_reader_")
    561                             + width + "x" + height + "_" + outputFrameCount + ".yuv";
    562 
    563                     Image image = null;
    564                     try {
    565                         if (mode == MODE_IMAGE) {
    566                             image = decoder.getOutputImage(res);
    567                         } else {
    568                             decoder.releaseOutputBuffer(res, doRender);
    569                             res = -1;
    570                             // Read image and verify
    571                             image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
    572                         }
    573                         validateImage(image, width, height, imageFormat, fileName);
    574 
    575                         if (checkSwirl) {
    576                             try {
    577                                 validateSwirl(image);
    578                             } catch (Throwable e) {
    579                                 dumpFile(fileName, getDataFromImage(image));
    580                                 throw e;
    581                             }
    582                         }
    583                     } finally {
    584                         if (image != null) {
    585                             image.close();
    586                         }
    587                     }
    588                 }
    589 
    590                 if (res >= 0) {
    591                     decoder.releaseOutputBuffer(res, false /* render */);
    592                 }
    593             }
    594         }
    595     }
    596 
    597     /**
    598      * Validate image based on format and size.
    599      *
    600      * @param image The image to be validated.
    601      * @param width The image width.
    602      * @param height The image height.
    603      * @param format The image format.
    604      * @param filePath The debug dump file path, null if don't want to dump to file.
    605      */
    606     public static void validateImage(
    607             Image image, int width, int height, int format, String filePath) {
    608         if (VERBOSE) {
    609             Plane[] imagePlanes = image.getPlanes();
    610             Log.v(TAG, "Image " + filePath + " Info:");
    611             Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
    612             Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
    613             Log.v(TAG, "Image timestamp:" + image.getTimestamp());
    614         }
    615 
    616         assertNotNull("Input image is invalid", image);
    617         assertEquals("Format doesn't match", format, image.getFormat());
    618         assertEquals("Width doesn't match", width, image.getCropRect().width());
    619         assertEquals("Height doesn't match", height, image.getCropRect().height());
    620 
    621         if(VERBOSE) Log.v(TAG, "validating Image");
    622         byte[] data = getDataFromImage(image);
    623         assertTrue("Invalid image data", data != null && data.length > 0);
    624 
    625         validateYuvData(data, width, height, format, image.getTimestamp());
    626 
    627         if (VERBOSE && filePath != null) {
    628             dumpFile(filePath, data);
    629         }
    630     }
    631 
    632     private static void validateSwirl(Image image) {
    633         Rect crop = image.getCropRect();
    634         final int NUM_SIDES = 4;
    635         final int step = 8;      // the width of the layers
    636         long[][] rawStats = new long[NUM_SIDES][10];
    637         int[][] colors = new int[][] {
    638             { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
    639         };
    640 
    641         // successively accumulate statistics for each layer of the swirl
    642         // by using overlapping rectangles, and the observation that
    643         // layer_i = rectangle_i - rectangle_(i+1)
    644         int lastLayer = 0;
    645         int layer = 0;
    646         boolean lastLayerValid = false;
    647         for (int pos = 0; ; pos += step) {
    648             Rect area = new Rect(pos - step, pos, crop.width() / 2, crop.height() + 2 * step - pos);
    649             if (area.isEmpty()) {
    650                 break;
    651             }
    652             area.offset(crop.left, crop.top);
    653             area.intersect(crop);
    654             for (int lr = 0; lr < 2; ++lr) {
    655                 long[] oneStat = CodecUtils.getRawStats(image, area);
    656                 if (VERBOSE) Log.v(TAG, "area=" + area + ", layer=" + layer + ", last="
    657                                     + lastLayer + ": " + Arrays.toString(oneStat));
    658                 for (int i = 0; i < oneStat.length; i++) {
    659                     rawStats[layer][i] += oneStat[i];
    660                     if (lastLayerValid) {
    661                         rawStats[lastLayer][i] -= oneStat[i];
    662                     }
    663                 }
    664                 if (VERBOSE && lastLayerValid) {
    665                     Log.v(TAG, "layer-" + lastLayer + ": " + Arrays.toString(rawStats[lastLayer]));
    666                     Log.v(TAG, Arrays.toString(CodecUtils.Raw2YUVStats(rawStats[lastLayer])));
    667                 }
    668                 // switch to the opposite side
    669                 layer ^= 2;      // NUM_SIDES / 2
    670                 lastLayer ^= 2;  // NUM_SIDES / 2
    671                 area.offset(crop.centerX() - area.left, 2 * (crop.centerY() - area.centerY()));
    672             }
    673 
    674             lastLayer = layer;
    675             lastLayerValid = true;
    676             layer = (layer + 1) % NUM_SIDES;
    677         }
    678 
    679         for (layer = 0; layer < NUM_SIDES; ++layer) {
    680             float[] stats = CodecUtils.Raw2YUVStats(rawStats[layer]);
    681             if (DEBUG) Log.d(TAG, "layer-" + layer + ": " + Arrays.toString(stats));
    682             if (VERBOSE) Log.v(TAG, Arrays.toString(rawStats[layer]));
    683 
    684             // check layer uniformity
    685             for (int i = 0; i < 3; i++) {
    686                 assertTrue("color of layer-" + layer + " is not uniform: "
    687                         + Arrays.toString(stats),
    688                         stats[3 + i] < COLOR_STDEV_ALLOWANCE);
    689             }
    690 
    691             // check layer color
    692             for (int i = 0; i < 3; i++) {
    693                 assertTrue("color of layer-" + layer + " mismatches target "
    694                         + Arrays.toString(colors[layer]) + " vs "
    695                         + Arrays.toString(Arrays.copyOf(stats, 3)),
    696                         Math.abs(stats[i] - colors[layer][i]) < COLOR_DELTA_ALLOWANCE);
    697             }
    698         }
    699     }
    700 
    701     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
    702             long ts) {
    703 
    704         assertTrue("YUV format must be one of the YUV_420_888, NV21, or YV12",
    705                 format == ImageFormat.YUV_420_888 ||
    706                 format == ImageFormat.NV21 ||
    707                 format == ImageFormat.YV12);
    708 
    709         if (VERBOSE) Log.v(TAG, "Validating YUV data");
    710         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
    711         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
    712     }
    713 
    714     private static void checkYuvFormat(int format) {
    715         if ((format != ImageFormat.YUV_420_888) &&
    716                 (format != ImageFormat.NV21) &&
    717                 (format != ImageFormat.YV12)) {
    718             fail("Wrong formats: " + format);
    719         }
    720     }
    721     /**
    722      * <p>Check android image format validity for an image, only support below formats:</p>
    723      *
    724      * <p>Valid formats are YUV_420_888/NV21/YV12 for video decoder</p>
    725      */
    726     private static void checkAndroidImageFormat(Image image) {
    727         int format = image.getFormat();
    728         Plane[] planes = image.getPlanes();
    729         switch (format) {
    730             case ImageFormat.YUV_420_888:
    731             case ImageFormat.NV21:
    732             case ImageFormat.YV12:
    733                 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
    734                 break;
    735             default:
    736                 fail("Unsupported Image Format: " + format);
    737         }
    738     }
    739 
    740     /**
    741      * Get a byte array image data from an Image object.
    742      * <p>
    743      * Read data from all planes of an Image into a contiguous unpadded,
    744      * unpacked 1-D linear byte array, such that it can be write into disk, or
    745      * accessed by software conveniently. It supports YUV_420_888/NV21/YV12
    746      * input Image format.
    747      * </p>
    748      * <p>
    749      * For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
    750      * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
    751      * (xstride = width, ystride = height for chroma and luma components).
    752      * </p>
    753      */
    754     private static byte[] getDataFromImage(Image image) {
    755         assertNotNull("Invalid image:", image);
    756         Rect crop = image.getCropRect();
    757         int format = image.getFormat();
    758         int width = crop.width();
    759         int height = crop.height();
    760         int rowStride, pixelStride;
    761         byte[] data = null;
    762 
    763         // Read image data
    764         Plane[] planes = image.getPlanes();
    765         assertTrue("Fail to get image planes", planes != null && planes.length > 0);
    766 
    767         // Check image validity
    768         checkAndroidImageFormat(image);
    769 
    770         ByteBuffer buffer = null;
    771 
    772         int offset = 0;
    773         data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
    774         byte[] rowData = new byte[planes[0].getRowStride()];
    775         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
    776         for (int i = 0; i < planes.length; i++) {
    777             int shift = (i == 0) ? 0 : 1;
    778             buffer = planes[i].getBuffer();
    779             assertNotNull("Fail to get bytebuffer from plane", buffer);
    780             rowStride = planes[i].getRowStride();
    781             pixelStride = planes[i].getPixelStride();
    782             assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
    783             if (VERBOSE) {
    784                 Log.v(TAG, "pixelStride " + pixelStride);
    785                 Log.v(TAG, "rowStride " + rowStride);
    786                 Log.v(TAG, "width " + width);
    787                 Log.v(TAG, "height " + height);
    788             }
    789             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
    790             int w = crop.width() >> shift;
    791             int h = crop.height() >> shift;
    792             buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
    793             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
    794             for (int row = 0; row < h; row++) {
    795                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
    796                 int length;
    797                 if (pixelStride == bytesPerPixel) {
    798                     // Special case: optimized read of the entire row
    799                     length = w * bytesPerPixel;
    800                     buffer.get(data, offset, length);
    801                     offset += length;
    802                 } else {
    803                     // Generic case: should work for any pixelStride but slower.
    804                     // Use intermediate buffer to avoid read byte-by-byte from
    805                     // DirectByteBuffer, which is very bad for performance
    806                     length = (w - 1) * pixelStride + bytesPerPixel;
    807                     buffer.get(rowData, 0, length);
    808                     for (int col = 0; col < w; col++) {
    809                         data[offset++] = rowData[col * pixelStride];
    810                     }
    811                 }
    812                 // Advance buffer the remainder of the row stride
    813                 if (row < h - 1) {
    814                     buffer.position(buffer.position() + rowStride - length);
    815                 }
    816             }
    817             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
    818         }
    819         return data;
    820     }
    821 
    822     private static void dumpFile(String fileName, byte[] data) {
    823         assertNotNull("fileName must not be null", fileName);
    824         assertNotNull("data must not be null", data);
    825 
    826         FileOutputStream outStream;
    827         try {
    828             Log.v(TAG, "output will be saved as " + fileName);
    829             outStream = new FileOutputStream(fileName);
    830         } catch (IOException ioe) {
    831             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
    832         }
    833 
    834         try {
    835             outStream.write(data);
    836             outStream.close();
    837         } catch (IOException ioe) {
    838             throw new RuntimeException("failed writing data to file " + fileName, ioe);
    839         }
    840     }
    841 
    842     private void createImageReader(
    843             int width, int height, int format, int maxNumImages,
    844             ImageReader.OnImageAvailableListener listener)  {
    845         closeImageReader();
    846 
    847         mReader = ImageReader.newInstance(width, height, format, maxNumImages);
    848         mReaderSurface = mReader.getSurface();
    849         mReader.setOnImageAvailableListener(listener, mHandler);
    850         if (VERBOSE) {
    851             Log.v(TAG, String.format("Created ImageReader size (%dx%d), format %d", width, height,
    852                     format));
    853         }
    854     }
    855 
    856     /**
    857      * Close the pending images then close current active {@link ImageReader} object.
    858      */
    859     private void closeImageReader() {
    860         if (mReader != null) {
    861             try {
    862                 // Close all possible pending images first.
    863                 Image image = mReader.acquireLatestImage();
    864                 if (image != null) {
    865                     image.close();
    866                 }
    867             } finally {
    868                 mReader.close();
    869                 mReader = null;
    870             }
    871         }
    872     }
    873 }
    874