Home | History | Annotate | Download | only in testingcamera2
      1 /*
      2  * Copyright (C) 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 com.android.testingcamera2;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Date;
     21 import java.util.LinkedList;
     22 import java.util.List;
     23 import java.util.Objects;
     24 import java.io.File;
     25 import java.io.FileOutputStream;
     26 import java.io.IOException;
     27 import java.io.OutputStream;
     28 import java.nio.ByteBuffer;
     29 import java.nio.ShortBuffer;
     30 import java.nio.channels.Channels;
     31 import java.nio.channels.WritableByteChannel;
     32 import java.text.SimpleDateFormat;
     33 
     34 import android.content.Context;
     35 import android.graphics.ImageFormat;
     36 import android.graphics.Bitmap;
     37 import android.graphics.BitmapFactory;
     38 import android.graphics.Color;
     39 import android.hardware.camera2.CameraCharacteristics;
     40 import android.hardware.camera2.DngCreator;
     41 import android.hardware.camera2.TotalCaptureResult;
     42 import android.hardware.camera2.params.StreamConfigurationMap;
     43 import android.media.Image;
     44 import android.media.ImageReader;
     45 import android.os.Environment;
     46 import android.os.SystemClock;
     47 import android.util.Size;
     48 import android.util.AttributeSet;
     49 import android.view.LayoutInflater;
     50 import android.view.Surface;
     51 import android.view.View;
     52 import android.widget.AdapterView;
     53 import android.widget.ArrayAdapter;
     54 import android.widget.Button;
     55 import android.widget.ImageView;
     56 import android.widget.LinearLayout;
     57 import android.widget.Spinner;
     58 import android.widget.AdapterView.OnItemSelectedListener;
     59 
     60 public class ImageReaderSubPane extends TargetSubPane {
     61 
     62     private static final int NO_FORMAT = -1;
     63     private static final int NO_SIZE = -1;
     64     private static final int NO_IMAGE = -1;
     65     private static final int MAX_BUFFER_COUNT = 25;
     66     private static final int DEFAULT_BUFFER_COUNT = 3;
     67 
     68     enum OutputFormat {
     69         JPEG(ImageFormat.JPEG),
     70         RAW16(ImageFormat.RAW_SENSOR),
     71         RAW10(ImageFormat.RAW10),
     72         YUV_420_888(ImageFormat.YUV_420_888);
     73 
     74         public final int imageFormat;
     75 
     76         OutputFormat(int imageFormat) {
     77             this.imageFormat = imageFormat;
     78         }
     79     };
     80 
     81     private Surface mSurface;
     82 
     83     private final Spinner mFormatSpinner;
     84     private final List<OutputFormat> mFormats = new ArrayList<>();
     85 
     86     private final Spinner mSizeSpinner;
     87     private Size[] mSizes;
     88     private final Spinner mCountSpinner;
     89     private Integer[] mCounts;
     90 
     91     private final ImageView mImageView;
     92 
     93     private int mCurrentCameraOrientation = 0;
     94     private int mCurrentUiOrientation = 0;
     95 
     96     private int mCurrentFormatId = NO_FORMAT;
     97     private int mCurrentSizeId = NO_SIZE;
     98     private CameraControlPane mCurrentCamera;
     99 
    100     private OutputFormat mConfiguredFormat = null;
    101     private Size mConfiguredSize = null;
    102     private int mConfiguredCount = 0;
    103 
    104     private ImageReader mReader = null;
    105     private final LinkedList<Image> mCurrentImages = new LinkedList<>();
    106     private int mCurrentImageIdx = NO_IMAGE;
    107 
    108     private int mRawShiftFactor = 0;
    109     private int mRawShiftRow = 0;
    110     private int mRawShiftCol = 0;
    111 
    112     public ImageReaderSubPane(Context context, AttributeSet attrs) {
    113         super(context, attrs);
    114 
    115         LayoutInflater inflater =
    116                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    117 
    118         inflater.inflate(R.layout.imagereader_target_subpane, this);
    119         this.setOrientation(VERTICAL);
    120 
    121         mFormatSpinner =
    122                 (Spinner) this.findViewById(R.id.target_subpane_image_reader_format_spinner);
    123         mFormatSpinner.setOnItemSelectedListener(mFormatSpinnerListener);
    124 
    125         mSizeSpinner = (Spinner) this.findViewById(R.id.target_subpane_image_reader_size_spinner);
    126         mSizeSpinner.setOnItemSelectedListener(mSizeSpinnerListener);
    127 
    128         mCountSpinner =
    129                 (Spinner) this.findViewById(R.id.target_subpane_image_reader_count_spinner);
    130         mCounts = new Integer[MAX_BUFFER_COUNT];
    131         for (int i = 0; i < mCounts.length; i++) {
    132             mCounts[i] = i + 1;
    133         }
    134         mCountSpinner.setAdapter(new ArrayAdapter<>(getContext(), R.layout.spinner_item,
    135                         mCounts));
    136         mCountSpinner.setSelection(DEFAULT_BUFFER_COUNT - 1);
    137 
    138         mImageView = (ImageView) this.findViewById(R.id.target_subpane_image_reader_view);
    139 
    140         Button b = (Button) this.findViewById(R.id.target_subpane_image_reader_prev_button);
    141         b.setOnClickListener(mPrevButtonListener);
    142 
    143         b = (Button) this.findViewById(R.id.target_subpane_image_reader_next_button);
    144         b.setOnClickListener(mNextButtonListener);
    145 
    146         b = (Button) this.findViewById(R.id.target_subpane_image_reader_save_button);
    147         b.setOnClickListener(mSaveButtonListener);
    148     }
    149 
    150     @Override
    151     public void setTargetCameraPane(CameraControlPane target) {
    152         mCurrentCamera = target;
    153         if (target != null) {
    154             updateFormats();
    155         } else {
    156             mSizeSpinner.setAdapter(null);
    157             mCurrentSizeId = NO_SIZE;
    158         }
    159     }
    160 
    161     @Override
    162     public void setUiOrientation(int orientation) {
    163         mCurrentUiOrientation = orientation;
    164     }
    165 
    166     private void updateFormats() {
    167         if (mCurrentCamera == null) {
    168             mFormatSpinner.setAdapter(null);
    169             mCurrentFormatId = NO_FORMAT;
    170             updateSizes();
    171             return;
    172         }
    173 
    174         OutputFormat oldFormat = null;
    175         if (mCurrentFormatId != NO_FORMAT) {
    176             oldFormat = mFormats.get(mCurrentFormatId);
    177         }
    178 
    179         CameraCharacteristics info = mCurrentCamera.getCharacteristics();
    180         StreamConfigurationMap streamConfigMap =
    181                 info.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    182 
    183         mFormats.clear();
    184         for (OutputFormat format : OutputFormat.values()) {
    185             if (streamConfigMap.isOutputSupportedFor(format.imageFormat)) {
    186                 mFormats.add(format);
    187             }
    188         }
    189 
    190         int newSelectionId = 0;
    191         for (int i = 0; i < mFormats.size(); i++) {
    192             if (mFormats.get(i).equals(oldFormat)) {
    193                 newSelectionId = i;
    194                 break;
    195             }
    196         }
    197 
    198         String[] outputFormatItems = new String[mFormats.size()];
    199         for (int i = 0; i < outputFormatItems.length; i++) {
    200             outputFormatItems[i] = mFormats.get(i).toString();
    201         }
    202 
    203         mFormatSpinner.setAdapter(new ArrayAdapter<>(getContext(), R.layout.spinner_item,
    204                         outputFormatItems));
    205         mFormatSpinner.setSelection(newSelectionId);
    206         mCurrentFormatId = newSelectionId;
    207 
    208         // Map sensor orientation to Surface.ROTATE_* constants
    209         final int SENSOR_ORIENTATION_TO_SURFACE_ROTATE = 90;
    210         mCurrentCameraOrientation = info.get(CameraCharacteristics.SENSOR_ORIENTATION) /
    211                 SENSOR_ORIENTATION_TO_SURFACE_ROTATE;
    212 
    213         // Get the max white level for raw data if any
    214         Integer maxLevel = info.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
    215         if (maxLevel != null) {
    216             int l = maxLevel;
    217             // Find number of bits to shift to map from 0..WHITE_LEVEL to 0..255
    218             for (mRawShiftFactor = 0; l > 255; mRawShiftFactor++) l >>= 1;
    219         } else {
    220             mRawShiftFactor = 0;
    221         }
    222 
    223         Integer cfa = info.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
    224         if (cfa != null) {
    225             switch (cfa) {
    226                 case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB:
    227                     mRawShiftRow = 0;
    228                     mRawShiftCol = 0;
    229                     break;
    230                 case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG:
    231                     mRawShiftRow = 0;
    232                     mRawShiftCol = 1;
    233                     break;
    234                 case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG:
    235                     mRawShiftRow = 1;
    236                     mRawShiftCol = 0;
    237                     break;
    238                 case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR:
    239                     mRawShiftRow = 1;
    240                     mRawShiftCol = 1;
    241                     break;
    242                 case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB:
    243                     mRawShiftRow = 0;
    244                     mRawShiftCol = 0;
    245 
    246                     break;
    247             }
    248         }
    249         updateSizes();
    250     }
    251 
    252     private void updateSizes() {
    253 
    254         if (mCurrentCamera == null) {
    255             mSizeSpinner.setAdapter(null);
    256             mCurrentSizeId = NO_SIZE;
    257             return;
    258         }
    259 
    260         Size oldSize = null;
    261         if (mCurrentSizeId != NO_SIZE) {
    262             oldSize = mSizes[mCurrentSizeId];
    263         }
    264 
    265         CameraCharacteristics info = mCurrentCamera.getCharacteristics();
    266         StreamConfigurationMap streamConfigMap =
    267                 info.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    268 
    269         mSizes = streamConfigMap.getOutputSizes(mFormats.get(mCurrentFormatId).imageFormat);
    270 
    271         int newSelectionId = 0;
    272         for (int i = 0; i < mSizes.length; i++) {
    273             if (mSizes[i].equals(oldSize)) {
    274                 newSelectionId = i;
    275                 break;
    276             }
    277         }
    278         String[] outputSizeItems = new String[mSizes.length];
    279         for (int i = 0; i < outputSizeItems.length; i++) {
    280             outputSizeItems[i] = mSizes[i].toString();
    281         }
    282 
    283         mSizeSpinner.setAdapter(new ArrayAdapter<>(getContext(), R.layout.spinner_item,
    284                         outputSizeItems));
    285         mSizeSpinner.setSelection(newSelectionId);
    286         mCurrentSizeId = newSelectionId;
    287     }
    288 
    289     private void updateImage() {
    290         if (mCurrentImageIdx == NO_IMAGE) return;
    291         Image img = mCurrentImages.get(mCurrentImageIdx);
    292 
    293         // Find rough scale factor to fit image into imageview to minimize processing overhead
    294         // Want to be one factor too large
    295         int SCALE_FACTOR = 2;
    296         while (mConfiguredSize.getWidth() > (mImageView.getWidth() * SCALE_FACTOR << 1) ) {
    297             SCALE_FACTOR <<= 1;
    298         }
    299 
    300         Bitmap imgBitmap = null;
    301         switch (img.getFormat()) {
    302             case ImageFormat.JPEG: {
    303                 ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
    304                 jpegBuffer.rewind();
    305                 byte[] jpegData = new byte[jpegBuffer.limit()];
    306                 jpegBuffer.get(jpegData);
    307                 BitmapFactory.Options opts = new BitmapFactory.Options();
    308                 opts.inSampleSize = SCALE_FACTOR;
    309                 imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, opts);
    310                 break;
    311             }
    312             case ImageFormat.YUV_420_888: {
    313                 ByteBuffer yBuffer = img.getPlanes()[0].getBuffer();
    314                 yBuffer.rewind();
    315                 int w = mConfiguredSize.getWidth() / SCALE_FACTOR;
    316                 int h = mConfiguredSize.getHeight() / SCALE_FACTOR;
    317                 byte[] row = new byte[mConfiguredSize.getWidth()];
    318                 int[] imgArray = new int[w * h];
    319                 for (int y = 0, j = 0; y < h; y++) {
    320                     yBuffer.position(y * SCALE_FACTOR * mConfiguredSize.getWidth());
    321                     yBuffer.get(row);
    322                     for (int x = 0, i = 0; x < w; x++) {
    323                         int yval = row[i] & 0xFF;
    324                         imgArray[j] = Color.rgb(yval, yval, yval);
    325                         i += SCALE_FACTOR;
    326                         j++;
    327                     }
    328                 }
    329                 imgBitmap = Bitmap.createBitmap(imgArray, w, h, Bitmap.Config.ARGB_8888);
    330                 break;
    331             }
    332             case ImageFormat.RAW_SENSOR: {
    333                 ShortBuffer rawBuffer = img.getPlanes()[0].getBuffer().asShortBuffer();
    334                 rawBuffer.rewind();
    335                 // Very rough nearest-neighbor downsample for display
    336                 int w = mConfiguredSize.getWidth() / SCALE_FACTOR;
    337                 int h = mConfiguredSize.getHeight() / SCALE_FACTOR;
    338                 short[] redRow = new short[mConfiguredSize.getWidth()];
    339                 short[] blueRow = new short[mConfiguredSize.getWidth()];
    340                 int[] imgArray = new int[w * h];
    341                 for (int y = 0, j = 0; y < h; y++) {
    342                     // Align to start of red row in the pair to sample from
    343                     rawBuffer.position(
    344                         (y * SCALE_FACTOR + mRawShiftRow) * mConfiguredSize.getWidth());
    345                     rawBuffer.get(redRow);
    346                     // Align to start of blue row in the pair to sample from
    347                     rawBuffer.position(
    348                         (y * SCALE_FACTOR + 1 - mRawShiftRow) * mConfiguredSize.getWidth());
    349                     rawBuffer.get(blueRow);
    350                     for (int x = 0, i = 0; x < w; x++, i += SCALE_FACTOR, j++) {
    351                         int r = redRow[i + mRawShiftCol] >> mRawShiftFactor;
    352                         int g = redRow[i + 1 - mRawShiftCol] >> mRawShiftFactor;
    353                         int b = blueRow[i + 1 - mRawShiftCol] >> mRawShiftFactor;
    354                         imgArray[j] = Color.rgb(r,g,b);
    355                     }
    356                 }
    357                 imgBitmap = Bitmap.createBitmap(imgArray, w, h, Bitmap.Config.ARGB_8888);
    358                 break;
    359             }
    360             case ImageFormat.RAW10: {
    361                 TLog.e("RAW10 viewing not implemented");
    362                 break;
    363             }
    364         }
    365         if (imgBitmap != null) {
    366             mImageView.setImageBitmap(imgBitmap);
    367         }
    368     }
    369 
    370     @Override
    371     public Surface getOutputSurface() {
    372         if (mCurrentSizeId == NO_SIZE ||
    373                 mCurrentFormatId == NO_FORMAT) {
    374             return null;
    375         }
    376         Size s = mSizes[mCurrentSizeId];
    377         OutputFormat f = mFormats.get(mCurrentFormatId);
    378         int c = (Integer) mCountSpinner.getSelectedItem();
    379         if (mReader == null ||
    380                 !Objects.equals(mConfiguredSize, s) ||
    381                 !Objects.equals(mConfiguredFormat, f) ||
    382                 mConfiguredCount != c) {
    383 
    384             if (mReader != null) {
    385                 mReader.close();
    386                 mCurrentImages.clear();
    387                 mCurrentImageIdx = NO_IMAGE;
    388             }
    389             mReader = ImageReader.newInstance(s.getWidth(), s.getHeight(), f.imageFormat, c);
    390             mReader.setOnImageAvailableListener(mImageListener, null);
    391             mConfiguredSize = s;
    392             mConfiguredFormat = f;
    393             mConfiguredCount = c;
    394         }
    395         return mReader.getSurface();
    396     }
    397 
    398     private final OnItemSelectedListener mFormatSpinnerListener = new OnItemSelectedListener() {
    399         @Override
    400         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
    401             mCurrentFormatId = pos;
    402             updateSizes();
    403         };
    404 
    405         @Override
    406         public void onNothingSelected(AdapterView<?> parent) {
    407             mCurrentFormatId = NO_FORMAT;
    408         };
    409     };
    410 
    411     private final OnItemSelectedListener mSizeSpinnerListener = new OnItemSelectedListener() {
    412         @Override
    413         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
    414             mCurrentSizeId = pos;
    415         };
    416 
    417         @Override
    418         public void onNothingSelected(AdapterView<?> parent) {
    419             mCurrentSizeId = NO_SIZE;
    420         };
    421     };
    422 
    423     private final OnClickListener mPrevButtonListener = new OnClickListener() {
    424         @Override
    425         public void onClick(View v) {
    426             if (mCurrentImageIdx != NO_IMAGE) {
    427                 int prevIdx = mCurrentImageIdx;
    428                 mCurrentImageIdx = (mCurrentImageIdx == 0) ?
    429                         (mCurrentImages.size() - 1) : (mCurrentImageIdx - 1);
    430                 if (prevIdx != mCurrentImageIdx) {
    431                     updateImage();
    432                 }
    433             }
    434         }
    435     };
    436 
    437     private final OnClickListener mNextButtonListener = new OnClickListener() {
    438         @Override
    439         public void onClick(View v) {
    440             if (mCurrentImageIdx != NO_IMAGE) {
    441                 int prevIdx = mCurrentImageIdx;
    442                 mCurrentImageIdx = (mCurrentImageIdx == mCurrentImages.size() - 1) ?
    443                         0 : (mCurrentImageIdx + 1);
    444                 if (prevIdx != mCurrentImageIdx) {
    445                     updateImage();
    446                 }
    447             }
    448         }
    449     };
    450 
    451     private final OnClickListener mSaveButtonListener = new OnClickListener() {
    452         @Override
    453         public void onClick(View v) {
    454             // TODO: Make async and coordinate with onImageAvailable
    455             if (mCurrentImageIdx != NO_IMAGE) {
    456                 Image img = mCurrentImages.get(mCurrentImageIdx);
    457                 try {
    458                     String name = saveImage(img);
    459                     TLog.i("Saved image as %s", name);
    460                 } catch (IOException e) {
    461                     TLog.e("Can't save file:", e);
    462                 }
    463             }
    464         }
    465     };
    466 
    467     private final ImageReader.OnImageAvailableListener mImageListener =
    468             new ImageReader.OnImageAvailableListener() {
    469         @Override
    470         public void onImageAvailable(ImageReader reader) {
    471             while (mCurrentImages.size() >= reader.getMaxImages()) {
    472                 Image oldest = mCurrentImages.remove();
    473                 oldest.close();
    474                 mCurrentImageIdx = Math.min(mCurrentImageIdx - 1, 0);
    475             }
    476             mCurrentImages.add(reader.acquireNextImage());
    477             if (mCurrentImageIdx == NO_IMAGE) {
    478                 mCurrentImageIdx = 0;
    479             }
    480             updateImage();
    481         }
    482     };
    483 
    484     private String saveImage(Image img) throws IOException {
    485         long timestamp = img.getTimestamp();
    486         File output = getOutputImageFile(img.getFormat(), timestamp);
    487         try (FileOutputStream out = new FileOutputStream(output)) {
    488             switch(img.getFormat()) {
    489                 case ImageFormat.JPEG: {
    490                     writeJpegImage(img, out);
    491                     break;
    492                 }
    493                 case ImageFormat.YUV_420_888: {
    494                     writeYuvImage(img, out);
    495                     break;
    496                 }
    497                 case ImageFormat.RAW_SENSOR: {
    498                     writeDngImage(img, out);
    499                     break;
    500                 }
    501                 case ImageFormat.RAW10: {
    502                     TLog.e("RAW10 saving not implemented");
    503                     break;
    504                 }
    505             }
    506         }
    507         return output.getName();
    508     }
    509 
    510     private void writeDngImage(Image img, OutputStream out) throws IOException {
    511         if (img.getFormat() != ImageFormat.RAW_SENSOR) {
    512             throw new IOException(
    513                     String.format("Unexpected Image format: %d, expected ImageFormat.RAW_SENSOR",
    514                             img.getFormat()));
    515         }
    516         long timestamp = img.getTimestamp();
    517         if (mCurrentCamera == null) {
    518             TLog.e("No camera availble for camera info, not saving DNG (timestamp %d)",
    519                     timestamp);
    520             throw new IOException("No camera info available");
    521         }
    522         TotalCaptureResult result = mCurrentCamera.getResultAt(timestamp);
    523         if (result == null) {
    524             TLog.e("No result matching raw image found, not saving DNG (timestamp %d)",
    525                     timestamp);
    526             throw new IOException("No matching result found");
    527         }
    528         CameraCharacteristics info = mCurrentCamera.getCharacteristics();
    529         try (DngCreator writer = new DngCreator(info, result)) {
    530             writer.writeImage(out, img);
    531         }
    532     }
    533 
    534     private void writeJpegImage(Image img, OutputStream out) throws IOException {
    535         if (img.getFormat() != ImageFormat.JPEG) {
    536             throw new IOException(
    537                     String.format("Unexpected Image format: %d, expected ImageFormat.JPEG",
    538                             img.getFormat()));
    539         }
    540         WritableByteChannel outChannel = Channels.newChannel(out);
    541         ByteBuffer jpegData = img.getPlanes()[0].getBuffer();
    542         jpegData.rewind();
    543         outChannel.write(jpegData);
    544     }
    545 
    546     private void writeYuvImage(Image img, OutputStream out)
    547             throws IOException {
    548         if (img.getFormat() != ImageFormat.YUV_420_888) {
    549             throw new IOException(
    550                     String.format("Unexpected Image format: %d, expected ImageFormat.YUV_420_888",
    551                             img.getFormat()));
    552         }
    553         WritableByteChannel outChannel = Channels.newChannel(out);
    554         for (int plane = 0; plane < 3; plane++) {
    555             Image.Plane colorPlane = img.getPlanes()[plane];
    556             ByteBuffer colorData = colorPlane.getBuffer();
    557             int subsampleFactor = (plane == 0) ? 1 : 2;
    558             int colorW = img.getWidth() / subsampleFactor;
    559             int colorH = img.getHeight() / subsampleFactor;
    560             colorData.rewind();
    561             colorData.limit(colorData.capacity());
    562             if (colorPlane.getPixelStride() == 1) {
    563                 // Can write contiguous rows
    564                 for (int y = 0, rowStart = 0; y < colorH;
    565                         y++, rowStart += colorPlane.getRowStride()) {
    566                     colorData.limit(rowStart + colorW);
    567                     colorData.position(rowStart);
    568                     outChannel.write(colorData);
    569                 }
    570             } else {
    571                 // Need to pack rows
    572                 byte[] row = new byte[colorW * colorPlane.getPixelStride()];
    573                 byte[] packedRow = new byte[colorW];
    574                 ByteBuffer packedRowBuffer = ByteBuffer.wrap(packedRow);
    575                 for (int y = 0, rowStart = 0; y < colorH;
    576                         y++, rowStart += colorPlane.getRowStride()) {
    577                     colorData.position(rowStart);
    578                     colorData.get(row);
    579                     for (int x = 0, i = 0; x < colorW;
    580                             x++, i += colorPlane.getPixelStride()) {
    581                         packedRow[x] = row[i];
    582                     }
    583                     packedRowBuffer.rewind();
    584                     outChannel.write(packedRowBuffer);
    585                 }
    586             }
    587         }
    588     }
    589 
    590     File getOutputImageFile(int type, long timestamp){
    591         // To be safe, you should check that the SDCard is mounted
    592         // using Environment.getExternalStorageState() before doing this.
    593 
    594         String state = Environment.getExternalStorageState();
    595         if (!Environment.MEDIA_MOUNTED.equals(state)) {
    596             return null;
    597         }
    598 
    599         File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
    600                   Environment.DIRECTORY_DCIM), "TestingCamera2");
    601         // This location works best if you want the created images to be shared
    602         // between applications and persist after your app has been uninstalled.
    603 
    604         // Create the storage directory if it does not exist
    605         if (!mediaStorageDir.exists()){
    606             if (!mediaStorageDir.mkdirs()){
    607                 TLog.e("Failed to create directory for pictures/video");
    608                 return null;
    609             }
    610         }
    611 
    612         // Create a media file name
    613 
    614         // Find out time now in the Date and boottime time bases.
    615         long nowMs = new Date().getTime();
    616         long nowBootTimeNs = SystemClock.elapsedRealtimeNanos();
    617 
    618         // Convert timestamp from boottime time base to the Date timebase
    619         // Slightly approximate, but close enough
    620         final long NS_PER_MS = 1000000l;
    621         long timestampMs = (nowMs * NS_PER_MS - nowBootTimeNs + timestamp) / NS_PER_MS;
    622 
    623         String timeStamp = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS").
    624                 format(new Date(timestampMs));
    625         File mediaFile = null;
    626         switch(type) {
    627             case ImageFormat.JPEG:
    628                 mediaFile = new File(mediaStorageDir.getPath() + File.separator +
    629                         "IMG_"+ timeStamp + ".jpg");
    630                 break;
    631             case ImageFormat.YUV_420_888:
    632                 mediaFile = new File(mediaStorageDir.getPath() + File.separator +
    633                         "IMG_"+ timeStamp + ".yuv");
    634                 break;
    635             case ImageFormat.RAW_SENSOR:
    636                 mediaFile = new File(mediaStorageDir.getPath() + File.separator +
    637                         "IMG_"+ timeStamp + ".dng");
    638                 break;
    639             case ImageFormat.RAW10:
    640                 mediaFile = new File(mediaStorageDir.getPath() + File.separator +
    641                         "IMG_"+ timeStamp + ".raw10");
    642                 break;
    643         }
    644 
    645         return mediaFile;
    646     }
    647 
    648 }
    649