Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright 2015 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;
     18 
     19 import android.graphics.ImageFormat;
     20 import android.graphics.PixelFormat;
     21 import android.media.Image.Plane;
     22 import android.util.Size;
     23 
     24 import libcore.io.Memory;
     25 
     26 import java.nio.ByteBuffer;
     27 
     28 /**
     29  * Package private utility class for hosting commonly used Image related methods.
     30  */
     31 class ImageUtils {
     32 
     33     /**
     34      * Only a subset of the formats defined in
     35      * {@link android.graphics.ImageFormat ImageFormat} and
     36      * {@link android.graphics.PixelFormat PixelFormat} are supported by
     37      * ImageReader. When reading RGB data from a surface, the formats defined in
     38      * {@link android.graphics.PixelFormat PixelFormat} can be used; when
     39      * reading YUV, JPEG or raw sensor data (for example, from the camera or video
     40      * decoder), formats from {@link android.graphics.ImageFormat ImageFormat}
     41      * are used.
     42      */
     43     public static int getNumPlanesForFormat(int format) {
     44         switch (format) {
     45             case ImageFormat.YV12:
     46             case ImageFormat.YUV_420_888:
     47             case ImageFormat.NV21:
     48                 return 3;
     49             case ImageFormat.NV16:
     50                 return 2;
     51             case PixelFormat.RGB_565:
     52             case PixelFormat.RGBA_8888:
     53             case PixelFormat.RGBX_8888:
     54             case PixelFormat.RGB_888:
     55             case ImageFormat.JPEG:
     56             case ImageFormat.YUY2:
     57             case ImageFormat.Y8:
     58             case ImageFormat.Y16:
     59             case ImageFormat.RAW_SENSOR:
     60             case ImageFormat.RAW_PRIVATE:
     61             case ImageFormat.RAW10:
     62             case ImageFormat.RAW12:
     63             case ImageFormat.DEPTH16:
     64             case ImageFormat.DEPTH_POINT_CLOUD:
     65             case ImageFormat.RAW_DEPTH:
     66                 return 1;
     67             case ImageFormat.PRIVATE:
     68                 return 0;
     69             default:
     70                 throw new UnsupportedOperationException(
     71                         String.format("Invalid format specified %d", format));
     72         }
     73     }
     74 
     75     /**
     76      * <p>
     77      * Copy source image data to destination Image.
     78      * </p>
     79      * <p>
     80      * Only support the copy between two non-{@link ImageFormat#PRIVATE PRIVATE} format
     81      * images with same properties (format, size, etc.). The data from the
     82      * source image will be copied to the byteBuffers from the destination Image
     83      * starting from position zero, and the destination image will be rewound to
     84      * zero after copy is done.
     85      * </p>
     86      *
     87      * @param src The source image to be copied from.
     88      * @param dst The destination image to be copied to.
     89      * @throws IllegalArgumentException If the source and destination images
     90      *             have different format, or one of the images is not copyable.
     91      */
     92     public static void imageCopy(Image src, Image dst) {
     93         if (src == null || dst == null) {
     94             throw new IllegalArgumentException("Images should be non-null");
     95         }
     96         if (src.getFormat() != dst.getFormat()) {
     97             throw new IllegalArgumentException("Src and dst images should have the same format");
     98         }
     99         if (src.getFormat() == ImageFormat.PRIVATE ||
    100                 dst.getFormat() == ImageFormat.PRIVATE) {
    101             throw new IllegalArgumentException("PRIVATE format images are not copyable");
    102         }
    103         if (src.getFormat() == ImageFormat.RAW_PRIVATE) {
    104             throw new IllegalArgumentException(
    105                     "Copy of RAW_OPAQUE format has not been implemented");
    106         }
    107         if (src.getFormat() == ImageFormat.RAW_DEPTH) {
    108             throw new IllegalArgumentException(
    109                     "Copy of RAW_DEPTH format has not been implemented");
    110         }
    111         if (!(dst.getOwner() instanceof ImageWriter)) {
    112             throw new IllegalArgumentException("Destination image is not from ImageWriter. Only"
    113                     + " the images from ImageWriter are writable");
    114         }
    115         Size srcSize = new Size(src.getWidth(), src.getHeight());
    116         Size dstSize = new Size(dst.getWidth(), dst.getHeight());
    117         if (!srcSize.equals(dstSize)) {
    118             throw new IllegalArgumentException("source image size " + srcSize + " is different"
    119                     + " with " + "destination image size " + dstSize);
    120         }
    121 
    122         Plane[] srcPlanes = src.getPlanes();
    123         Plane[] dstPlanes = dst.getPlanes();
    124         ByteBuffer srcBuffer = null;
    125         ByteBuffer dstBuffer = null;
    126         for (int i = 0; i < srcPlanes.length; i++) {
    127             int srcRowStride = srcPlanes[i].getRowStride();
    128             int dstRowStride = dstPlanes[i].getRowStride();
    129             srcBuffer = srcPlanes[i].getBuffer();
    130             dstBuffer = dstPlanes[i].getBuffer();
    131             if (!(srcBuffer.isDirect() && dstBuffer.isDirect())) {
    132                 throw new IllegalArgumentException("Source and destination ByteBuffers must be"
    133                         + " direct byteBuffer!");
    134             }
    135             if (srcPlanes[i].getPixelStride() != dstPlanes[i].getPixelStride()) {
    136                 throw new IllegalArgumentException("Source plane image pixel stride " +
    137                         srcPlanes[i].getPixelStride() +
    138                         " must be same as destination image pixel stride " +
    139                         dstPlanes[i].getPixelStride());
    140             }
    141 
    142             int srcPos = srcBuffer.position();
    143             srcBuffer.rewind();
    144             dstBuffer.rewind();
    145             if (srcRowStride == dstRowStride) {
    146                 // Fast path, just copy the content if the byteBuffer all together.
    147                 dstBuffer.put(srcBuffer);
    148             } else {
    149                 // Source and destination images may have different alignment requirements,
    150                 // therefore may have different strides. Copy row by row for such case.
    151                 int srcOffset = srcBuffer.position();
    152                 int dstOffset = dstBuffer.position();
    153                 Size effectivePlaneSize = getEffectivePlaneSizeForImage(src, i);
    154                 int srcByteCount = effectivePlaneSize.getWidth() * srcPlanes[i].getPixelStride();
    155                 for (int row = 0; row < effectivePlaneSize.getHeight(); row++) {
    156                     if (row == effectivePlaneSize.getHeight() - 1) {
    157                         // Special case for NV21 backed YUV420_888: need handle the last row
    158                         // carefully to avoid memory corruption. Check if we have enough bytes to
    159                         // copy.
    160                         int remainingBytes = srcBuffer.remaining() - srcOffset;
    161                         if (srcByteCount > remainingBytes) {
    162                             srcByteCount = remainingBytes;
    163                         }
    164                     }
    165                     directByteBufferCopy(srcBuffer, srcOffset, dstBuffer, dstOffset, srcByteCount);
    166                     srcOffset += srcRowStride;
    167                     dstOffset += dstRowStride;
    168                 }
    169             }
    170 
    171             srcBuffer.position(srcPos);
    172             dstBuffer.rewind();
    173         }
    174     }
    175 
    176     /**
    177      * Return the estimated native allocation size in bytes based on width, height, format,
    178      * and number of images.
    179      *
    180      * <p>This is a very rough estimation and should only be used for native allocation
    181      * registration in VM so it can be accounted for during GC.</p>
    182      *
    183      * @param width The width of the images.
    184      * @param height The height of the images.
    185      * @param format The format of the images.
    186      * @param numImages The number of the images.
    187      */
    188     public static int getEstimatedNativeAllocBytes(int width, int height, int format,
    189             int numImages) {
    190         double estimatedBytePerPixel;
    191         switch (format) {
    192             // 10x compression from RGB_888
    193             case ImageFormat.JPEG:
    194             case ImageFormat.DEPTH_POINT_CLOUD:
    195                 estimatedBytePerPixel = 0.3;
    196                 break;
    197             case ImageFormat.Y8:
    198                 estimatedBytePerPixel = 1.0;
    199                 break;
    200             case ImageFormat.RAW10:
    201                 estimatedBytePerPixel = 1.25;
    202                 break;
    203             case ImageFormat.YV12:
    204             case ImageFormat.YUV_420_888:
    205             case ImageFormat.NV21:
    206             case ImageFormat.RAW12:
    207             case ImageFormat.PRIVATE: // A rough estimate because the real size is unknown.
    208                 estimatedBytePerPixel = 1.5;
    209                 break;
    210             case ImageFormat.NV16:
    211             case PixelFormat.RGB_565:
    212             case ImageFormat.YUY2:
    213             case ImageFormat.Y16:
    214             case ImageFormat.RAW_DEPTH:
    215             case ImageFormat.RAW_SENSOR:
    216             case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown
    217             case ImageFormat.DEPTH16:
    218                 estimatedBytePerPixel = 2.0;
    219                 break;
    220             case PixelFormat.RGB_888:
    221                 estimatedBytePerPixel = 3.0;
    222                 break;
    223             case PixelFormat.RGBA_8888:
    224             case PixelFormat.RGBX_8888:
    225                 estimatedBytePerPixel = 4.0;
    226                 break;
    227             default:
    228                 throw new UnsupportedOperationException(
    229                         String.format("Invalid format specified %d", format));
    230         }
    231 
    232         return (int)(width * height * estimatedBytePerPixel * numImages);
    233     }
    234 
    235     private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) {
    236         switch (image.getFormat()) {
    237             case ImageFormat.YV12:
    238             case ImageFormat.YUV_420_888:
    239             case ImageFormat.NV21:
    240                 if (planeIdx == 0) {
    241                     return new Size(image.getWidth(), image.getHeight());
    242                 } else {
    243                     return new Size(image.getWidth() / 2, image.getHeight() / 2);
    244                 }
    245             case ImageFormat.NV16:
    246                 if (planeIdx == 0) {
    247                     return new Size(image.getWidth(), image.getHeight());
    248                 } else {
    249                     return new Size(image.getWidth(), image.getHeight() / 2);
    250                 }
    251             case PixelFormat.RGB_565:
    252             case PixelFormat.RGBA_8888:
    253             case PixelFormat.RGBX_8888:
    254             case PixelFormat.RGB_888:
    255             case ImageFormat.JPEG:
    256             case ImageFormat.YUY2:
    257             case ImageFormat.Y8:
    258             case ImageFormat.Y16:
    259             case ImageFormat.RAW_SENSOR:
    260             case ImageFormat.RAW10:
    261             case ImageFormat.RAW12:
    262             case ImageFormat.RAW_DEPTH:
    263                 return new Size(image.getWidth(), image.getHeight());
    264             case ImageFormat.PRIVATE:
    265                 return new Size(0, 0);
    266             default:
    267                 throw new UnsupportedOperationException(
    268                         String.format("Invalid image format %d", image.getFormat()));
    269         }
    270     }
    271 
    272     private static void directByteBufferCopy(ByteBuffer srcBuffer, int srcOffset,
    273             ByteBuffer dstBuffer, int dstOffset, int srcByteCount) {
    274         Memory.memmove(dstBuffer, dstOffset, srcBuffer, srcOffset, srcByteCount);
    275     }
    276 }
    277