• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OutputFormat(int imageFormat)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 
ImageReaderSubPane(Context context, AttributeSet attrs)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
setTargetCameraPane(CameraControlPane target)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
setUiOrientation(int orientation)162     public void setUiOrientation(int orientation) {
163         mCurrentUiOrientation = orientation;
164     }
165 
updateFormats()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 
updateSizes()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 
updateImage()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
getOutputSurface()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 
saveImage(Image img)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 
writeDngImage(Image img, OutputStream out)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 
writeJpegImage(Image img, OutputStream out)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 
writeYuvImage(Image img, OutputStream out)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 
getOutputImageFile(int type, long timestamp)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