diff options
35 files changed, 3601 insertions, 410 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 13e4f5d6f..ac727e2a2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4,7 +4,7 @@ package="org.codeaurora.snapcam"> <uses-sdk - android:minSdkVersion="21" + android:minSdkVersion="23" android:targetSdkVersion="23" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> diff --git a/res/layout/capture_module.xml b/res/layout/capture_module.xml index c6adf0239..a68af5e08 100644 --- a/res/layout/capture_module.xml +++ b/res/layout/capture_module.xml @@ -40,8 +40,8 @@ <com.android.camera.ui.AutoFitSurfaceView android:id="@+id/mdp_preview_content2" - android:layout_width="100dp" - android:layout_height="100dp" /> + android:layout_width="300dp" + android:layout_height="300dp" /> </FrameLayout> <View diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml index 10017192b..9c5b2c0d0 100644 --- a/res/layout/photo_module.xml +++ b/res/layout/photo_module.xml @@ -65,7 +65,7 @@ android:id="@+id/autohdr_view" android:layout_width="200dip" android:layout_height="200dip" - android:layout_marginTop="15dip" + android:layout_marginTop="50dip" android:layout_marginLeft="15dip" /> </RelativeLayout> <ImageView diff --git a/res/layout/video_module.xml b/res/layout/video_module.xml index 1c1db4444..20d7be1d2 100644 --- a/res/layout/video_module.xml +++ b/res/layout/video_module.xml @@ -26,8 +26,7 @@ <SurfaceView android:id="@+id/mdp_preview_content" android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" /> + android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:layout_width="match_parent" @@ -43,8 +42,7 @@ android:id="@+id/preview_cover" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@android:color/black" - android:visibility="gone" /> + android:background="@android:color/black" /> <View android:id="@+id/flash_overlay" android:layout_width="match_parent" diff --git a/res/values/qcomarrays.xml b/res/values/qcomarrays.xml index a4c463735..d3218bdaf 100644 --- a/res/values/qcomarrays.xml +++ b/res/values/qcomarrays.xml @@ -863,5 +863,25 @@ <item>@string/pref_camera_dual_camera_value_bayer</item> <item>@string/pref_camera_dual_camera_value_mono</item> </string-array> + + <string-array name="pref_camera_mono_preview_entries" translatable="true"> + <item>@string/pref_camera_mono_preview_entry_on</item> + <item>@string/pref_camera_mono_preview_entry_off</item> + </string-array> + + <string-array name="pref_camera_mono_preview_entryvalues" translatable="false"> + <item>@string/pref_camera_mono_preview_value_on</item> + <item>@string/pref_camera_mono_preview_value_off</item> + </string-array> + + <string-array name="pref_camera_clearsight_entries" translatable="true"> + <item>@string/pref_camera_clearsight_entry_on</item> + <item>@string/pref_camera_clearsight_entry_off</item> + </string-array> + + <string-array name="pref_camera_clearsight_entryvalues" translatable="false"> + <item>@string/pref_camera_clearsight_value_on</item> + <item>@string/pref_camera_clearsight_value_off</item> + </string-array> </resources> diff --git a/res/values/qcomstrings.xml b/res/values/qcomstrings.xml index 0e21bb5e6..ea90cf72a 100644 --- a/res/values/qcomstrings.xml +++ b/res/values/qcomstrings.xml @@ -277,7 +277,7 @@ <string name="pref_camera_cds_default" translatable="false">on</string> <!-- Default video cds mode setting--> - <string name="pref_camera_video_cds_default" translatable="false">on</string> + <string name="pref_camera_video_cds_default" translatable="false">off</string> <!-- Settings menu, setting title text for tnr mode--> <string name="pref_camera_tnr_title">TNR mode</string> @@ -289,7 +289,7 @@ <string name="pref_camera_tnr_default" translatable="false">off</string> <!-- Default video tnr mode setting--> - <string name="pref_camera_video_tnr_default" translatable="false">off</string> + <string name="pref_camera_video_tnr_default" translatable="false">on</string> <!-- Default face detection setting. --> <string name="pref_camera_facedetection_default" translatable="false">off</string> @@ -953,9 +953,8 @@ <string name="pref_camera_camera2_value_enable">enable</string> <string name="pref_camera_camera2_value_disable">disable</string> - <string name="pref_camera_dual_camera_title">Dual Camera Mode</string> - <string name="pref_camera_dual_camera_default">bayer</string> + <string name="pref_camera_dual_camera_default">dual</string> <string name="pref_camera_dual_camera_entry_dual">Dual-camera Linked</string> <string name="pref_camera_dual_camera_entry_bayer">Single Bayer Camera</string> <string name="pref_camera_dual_camera_entry_mono">Single Mono Camera</string> @@ -963,5 +962,24 @@ <string name="pref_camera_dual_camera_value_dual">dual</string> <string name="pref_camera_dual_camera_value_bayer">bayer</string> <string name="pref_camera_dual_camera_value_mono">mono</string> + + <string name="pref_camera_mono_preview_title">Mono Preview</string> + <string name="pref_camera_mono_preview_default">off</string> + <string name="pref_camera_mono_preview_entry_on">On</string> + <string name="pref_camera_mono_preview_entry_off">Off</string> + + <string name="pref_camera_mono_preview_value_on">on</string> + <string name="pref_camera_mono_preview_value_off">off</string> + + <string name="pref_camera_clearsight_title">ClearSight</string> + <string name="pref_camera_clearsight_default" translatable="false">off</string> + <string name="pref_camera_clearsight_entry_on">On</string> + <string name="pref_camera_clearsight_entry_off">Off</string> + + <string name="pref_camera_clearsight_value_on" translatable="false">on</string> + <string name="pref_camera_clearsight_value_off" translatable="false">off</string> + + <string name="clearsight_capture_success">ClearSight capture successful</string> + <string name="clearsight_capture_fail">ClearSight capture failed</string> </resources> diff --git a/res/xml/camera_preferences.xml b/res/xml/camera_preferences.xml index 028ff4dbf..5fafb855b 100644 --- a/res/xml/camera_preferences.xml +++ b/res/xml/camera_preferences.xml @@ -379,4 +379,19 @@ camera:title="@string/pref_camera_dual_camera_title" camera:entries="@array/pref_camera_dual_camera_entries" camera:entryValues="@array/pref_camera_dual_camera_entryvalues" /> + + <ListPreference + camera:defaultValue="@string/pref_camera_mono_preview_default" + camera:entries="@array/pref_camera_mono_preview_entries" + camera:entryValues="@array/pref_camera_mono_preview_entryvalues" + camera:key="pref_camera_mono_preview_key" + camera:title="@string/pref_camera_mono_preview_title" /> + + <ListPreference + camera:defaultValue="@string/pref_camera_clearsight_default" + camera:entries="@array/pref_camera_clearsight_entries" + camera:entryValues="@array/pref_camera_clearsight_entryvalues" + camera:key="pref_camera_clearsight_key" + camera:title="@string/pref_camera_clearsight_title" /> + </PreferenceGroup> diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index 80387a946..fafb40839 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -1813,7 +1813,9 @@ public class CameraActivity extends Activity public void onModuleSelected(int moduleIndex) { if (moduleIndex == 0 && CAMERA_2_ON) moduleIndex = ModuleSwitcher.CAPTURE_MODULE_INDEX; if (mCurrentModuleIndex == moduleIndex) { - return; + if (mCurrentModuleIndex != ModuleSwitcher.CAPTURE_MODULE_INDEX) { + return; + } } CameraHolder.instance().keep(); closeModule(mCurrentModule); @@ -1840,7 +1842,6 @@ public class CameraActivity extends Activity mCameraVideoModuleRootView.setVisibility(View.GONE); mCameraPanoModuleRootView.setVisibility(View.GONE); mCameraCaptureModuleRootView.setVisibility(View.GONE); - mCameraRootFrame.removeAllViews(); mCurrentModuleIndex = moduleIndex; switch (moduleIndex) { case ModuleSwitcher.VIDEO_MODULE_INDEX: @@ -1851,7 +1852,6 @@ public class CameraActivity extends Activity mVideoModule.reinit(); } mCurrentModule = mVideoModule; - mCameraRootFrame.addView(mCameraVideoModuleRootView); mCameraVideoModuleRootView.setVisibility(View.VISIBLE); break; @@ -1863,7 +1863,6 @@ public class CameraActivity extends Activity mPhotoModule.reinit(); } mCurrentModule = mPhotoModule; - mCameraRootFrame.addView(mCameraPhotoModuleRootView); mCameraPhotoModuleRootView.setVisibility(View.VISIBLE); break; @@ -1873,7 +1872,6 @@ public class CameraActivity extends Activity mPanoModule.init(this, mCameraPanoModuleRootView); } mCurrentModule = mPanoModule; - mCameraRootFrame.addView(mCameraPanoModuleRootView); mCameraPanoModuleRootView.setVisibility(View.VISIBLE); break; @@ -1883,7 +1881,6 @@ public class CameraActivity extends Activity mCaptureModule.init(this, mCameraCaptureModuleRootView); } mCurrentModule = mCaptureModule; - mCameraRootFrame.addView(mCameraCaptureModuleRootView); mCameraCaptureModuleRootView.setVisibility(View.VISIBLE); break; case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: //Unused module for now @@ -1897,7 +1894,6 @@ public class CameraActivity extends Activity mPhotoModule.reinit(); } mCurrentModule = mPhotoModule; - mCameraRootFrame.addView(mCameraPhotoModuleRootView); mCameraPhotoModuleRootView.setVisibility(View.VISIBLE); break; } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 48d161d7c..a12ce5de0 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -248,6 +248,8 @@ public class CameraSettings { public static final String KEY_CAMERA2 = "pref_camera_camera2_key"; public static final String KEY_DUAL_CAMERA = "pref_camera_dual_camera_key"; + public static final String KEY_MONO_PREVIEW = "pref_camera_mono_preview_key"; + public static final String KEY_CLEARSIGHT = "pref_camera_clearsight_key"; public static final String KEY_REFOCUS_PROMPT = "refocus-prompt"; @@ -280,7 +282,7 @@ public class CameraSettings { //video encoders VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H263, "h263"); VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H264, "h264"); - VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H265, "h265"); + // VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H265, "h265"); VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.MPEG_4_SP, "m4v"); //video qualities diff --git a/src/com/android/camera/CaptureMenu.java b/src/com/android/camera/CaptureMenu.java index dbc8132be..8b8f5c357 100644 --- a/src/com/android/camera/CaptureMenu.java +++ b/src/com/android/camera/CaptureMenu.java @@ -26,6 +26,7 @@ import android.content.SharedPreferences; import android.graphics.Rect; import android.preference.PreferenceManager; import android.text.TextUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -91,14 +92,26 @@ public class CaptureMenu extends MenuController mOtherKeys1 = new String[]{ CameraSettings.KEY_FLASH_MODE, + CameraSettings.KEY_RECORD_LOCATION, + CameraSettings.KEY_JPEG_QUALITY, + CameraSettings.KEY_CAMERA_SAVEPATH, + CameraSettings.KEY_WHITE_BALANCE, CameraSettings.KEY_CAMERA2, - CameraSettings.KEY_DUAL_CAMERA + CameraSettings.KEY_DUAL_CAMERA, + CameraSettings.KEY_CLEARSIGHT }; + //Todo: 2nd string to contain only developer settings mOtherKeys2 = new String[]{ CameraSettings.KEY_FLASH_MODE, + CameraSettings.KEY_RECORD_LOCATION, + CameraSettings.KEY_JPEG_QUALITY, + CameraSettings.KEY_CAMERA_SAVEPATH, + CameraSettings.KEY_WHITE_BALANCE, CameraSettings.KEY_CAMERA2, - CameraSettings.KEY_DUAL_CAMERA + CameraSettings.KEY_DUAL_CAMERA, + CameraSettings.KEY_CLEARSIGHT, + CameraSettings.KEY_MONO_PREVIEW }; } @@ -106,9 +119,8 @@ public class CaptureMenu extends MenuController @Override // Hit when an item in a popup gets selected public void onListPrefChanged(ListPreference pref) { - animateFadeOut(mListSubMenu, 2); onSettingChanged(pref); - ((ListMenu) mListMenu).resetHighlight(); + closeView(); } public boolean handleBackKey() { @@ -384,6 +396,14 @@ public class CaptureMenu extends MenuController listMenu.initialize(mPreferenceGroup, keys); mListMenu = listMenu; + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_DUAL_CAMERA); + if (!pref.getValue().equals("dual")) { + setPreference(CameraSettings.KEY_MONO_PREVIEW, "off"); + mListMenu.setPreferenceEnabled(CameraSettings.KEY_MONO_PREVIEW, false); + setPreference(CameraSettings.KEY_CLEARSIGHT, "off"); + mListMenu.setPreferenceEnabled(CameraSettings.KEY_CLEARSIGHT, false); + } + if (mListener != null) { mListener.onSharedPreferenceChanged(); } @@ -516,18 +536,32 @@ public class CaptureMenu extends MenuController @Override public void onSettingChanged(ListPreference pref) { super.onSettingChanged(pref); - if (same(pref, CameraSettings.KEY_CAMERA2, "enable")) { + String key = pref.getKey(); + String value = pref.getValue(); + Log.d(TAG, "" + key + " " + value); + //Todo: restructure by using switch and create function for each case + if (key.equals(CameraSettings.KEY_CAMERA2)) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(mActivity); - prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, true).apply(); - CameraActivity.CAMERA_2_ON = true; + if (value.equals("enable")) { + prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, true).apply(); + CameraActivity.CAMERA_2_ON = true; + mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX); + } else if (value.equals("disable")) { + prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, false).apply(); + CameraActivity.CAMERA_2_ON = false; + mActivity.onModuleSelected(ModuleSwitcher.PHOTO_MODULE_INDEX); + } + } else if (key.equals(CameraSettings.KEY_DUAL_CAMERA)) { + boolean changeMode = CaptureModule.setMode(value); + if (changeMode) mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX); + } else if (key.equals(CameraSettings.KEY_MONO_PREVIEW)) { + if (value.equals("on")) { + } else if (value.equals("off")) { + } + } else if (key.equals(CameraSettings.KEY_CLEARSIGHT)) { + // restart module to re-create sessions and callbacks mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX); - } else if (notSame(pref, CameraSettings.KEY_CAMERA2, "enable")) { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(mActivity); - prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, false).apply(); - CameraActivity.CAMERA_2_ON = false; - mActivity.onModuleSelected(ModuleSwitcher.PHOTO_MODULE_INDEX); } } diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java index 604a705f3..382e691b1 100644 --- a/src/com/android/camera/CaptureModule.java +++ b/src/com/android/camera/CaptureModule.java @@ -19,54 +19,78 @@ package com.android.camera; -import android.annotation.TargetApi; -import android.app.Activity; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.codeaurora.snapcam.R; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.ImageFormat; +import android.graphics.Point; import android.graphics.Rect; +import android.graphics.YuvImage; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.InputConfiguration; +import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.CameraProfile; import android.media.Image; +import android.media.Image.Plane; import android.media.ImageReader; +import android.media.ImageWriter; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; import android.util.Log; import android.util.Size; import android.util.SparseIntArray; import android.view.KeyEvent; +import android.view.OrientationEventListener; import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; +import android.widget.Toast; import com.android.camera.PhotoModule.NamedImages; import com.android.camera.PhotoModule.NamedImages.NamedEntity; +import com.android.camera.ui.RotateTextToast; +import com.android.camera.util.CameraUtil; +import com.android.camera.util.ClearSightNativeEngine; + +public class CaptureModule implements CameraModule, PhotoController, + MediaSaveService.Listener { + public static final int DUAL_MODE = 0; + public static final int BAYER_MODE = 1; + public static final int MONO_MODE = 2; + private static final int OPEN_CAMERA = 0; + private static final int MAX_NUM_CAM = 3; + private static final long TIMESTAMP_THRESHOLD_NS = 10*1000000; // 10 ms -import org.codeaurora.snapcam.R; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -public class CaptureModule implements CameraModule, PhotoController { - private static final int NUMCAM = 1; /** * Conversion from screen rotation to JPEG orientation. */ @@ -91,7 +115,18 @@ public class CaptureModule implements CameraModule, PhotoController { * Camera state: Picture was taken. */ private static final int STATE_PICTURE_TAKEN = 4; + //Todo: Read ids from the device dynamically + private static final int BAYER_ID = 0; + private static final int MONO_ID = 1; private static final String TAG = "SnapCam_CaptureModule"; + private static int MODE = DUAL_MODE; + + private static final int MSG_START_CAPTURE = 0; + private static final int MSG_NEW_IMG = 1; + private static final int MSG_NEW_RESULT = 2; + private static final int MSG_SAVE = 4; + + private static final int NUM_IMAGES_TO_BURST = 4; static { ORIENTATIONS.append(Surface.ROTATION_0, 90); @@ -100,60 +135,74 @@ public class CaptureModule implements CameraModule, PhotoController { ORIENTATIONS.append(Surface.ROTATION_270, 180); } + MeteringRectangle[][] mAFRegions = new MeteringRectangle[MAX_NUM_CAM][]; + CaptureRequest.Key<Byte> BayerMonoLinkEnableKey = + new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data.enable", + Byte.class); + CaptureRequest.Key<Byte> BayerMonoLinkMainKey = + new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data.is_main", + Byte.class); + CaptureRequest.Key<Integer> BayerMonoLinkSessionIdKey = + new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data" + + ".related_camera_id", Integer.class); + + CaptureResult.Key<Byte> OTP_CALIB_BLOB = + new CaptureResult.Key<>("org.codeaurora.qcamera3.dualcam_calib_meta_data.dualcam_calib_meta_data_blob", + Byte.class); + + private int mLastResultAFState = -1; + private Rect[] mCropRegion = new Rect[MAX_NUM_CAM]; + private boolean mAutoFocusSupported; + // The degrees of the device rotated clockwise from its natural orientation. + private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; + private int mJpegQuality; + private Map<String, String> mSettings = new HashMap<String, String>(); + private boolean mFirstTimeInitialized; + private boolean mInitialized = false; + private boolean mIsLinked = false; private long mCaptureStartTime; + private boolean mPaused = true; private boolean mSurfaceReady = false; - private boolean mCameraOpened = false; - private CameraDevice[] mCameraDevice = new CameraDevice[NUMCAM]; - private String[] mCameraId = new String[NUMCAM]; + private boolean[] mCameraOpened = new boolean[MAX_NUM_CAM]; + private CameraDevice[] mCameraDevice = new CameraDevice[MAX_NUM_CAM]; + private String[] mCameraId = new String[MAX_NUM_CAM]; private CaptureUI mUI; private CameraActivity mActivity; private PreferenceGroup mPreferenceGroup; private ComboPreferences mPreferences; - private CaptureRequest.Builder[] mCaptureBuilder = new CaptureRequest.Builder[NUMCAM]; - private final CameraPreference.OnPreferenceChangedListener prefListener = new - CameraPreference.OnPreferenceChangedListener() { - @Override - public void onSharedPreferenceChanged(ListPreference pref) { - applyPreference(0, pref); - mUI.overrideSettings(pref.getKey(), null); - } - - @Override - public void onSharedPreferenceChanged() { - } - - @Override - public void onRestorePreferencesClicked() { - } - - @Override - public void onOverriddenPreferencesClicked() { - } - - @Override - public void onCameraPickerClicked(int cameraId) { - } - }; + private CameraCharacteristics[] mCharacteristics = new CameraCharacteristics[MAX_NUM_CAM]; + private List<Integer> mCharacteristicsIndex; + private float mZoomValue = 1f; + private FocusStateListener mFocusStateListener; + private LocationManager mLocationManager; /** * A {@link CameraCaptureSession } for camera preview. */ - private CameraCaptureSession[] mCaptureSession = new CameraCaptureSession[NUMCAM]; + private CameraCaptureSession[] mCaptureSession = new CameraCaptureSession[MAX_NUM_CAM]; /** * An additional thread for running tasks that shouldn't block the UI. */ private HandlerThread mCameraThread; private HandlerThread mImageAvailableThread; private HandlerThread mCallbackThread; + private HandlerThread mImageProcessThread; + private HandlerThread mImageReprocessThread; + /** * A {@link Handler} for running tasks in the background. */ private Handler mCameraHandler; private Handler mImageAvailableHandler; private Handler mCallbackHandler; + private ImageProcessHandler mImageProcessHandler; + private ImageReprocessHandler mImageReprocessHandler; + /** * An {@link ImageReader} that handles still image capture. */ - private ImageReader[] mImageReader = new ImageReader[NUMCAM]; + private ImageReader[] mImageReader = new ImageReader[MAX_NUM_CAM]; + private ImageReader[] mReprocessImageReader = new ImageReader[MAX_NUM_CAM]; + private ImageWriter[] mImageWriter = new ImageWriter[MAX_NUM_CAM]; private NamedImages mNamedImages; private ContentResolver mContentResolver; private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener = @@ -165,47 +214,482 @@ public class CaptureModule implements CameraModule, PhotoController { } } }; - /** - * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a - * still image is ready to be saved. - */ - private final ImageReader.OnImageAvailableListener mOnImageAvailableListener - = new ImageReader.OnImageAvailableListener() { + + private class ReprocessableImage { + final Image mImage; + final TotalCaptureResult mCaptureResult; + + ReprocessableImage(Image image, TotalCaptureResult result) { + mImage = image; + mCaptureResult = result; + } + } + + private abstract class ImageAvailableListener implements ImageReader.OnImageAvailableListener { + int mCamId; + + ImageAvailableListener(int cameraId) { + mCamId = cameraId; + } + } + + private abstract class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback { + int mCamId; + + CameraCaptureCallback(int cameraId) { + mCamId = cameraId; + } + } + + private class ImageProcessHandler extends Handler { + private ArrayDeque<ReprocessableImage> mBayerFrames = new ArrayDeque<ReprocessableImage>( + NUM_IMAGES_TO_BURST); + private ArrayDeque<ReprocessableImage> mMonoFrames = new ArrayDeque<ReprocessableImage>( + NUM_IMAGES_TO_BURST); + private ArrayDeque<TotalCaptureResult> mBayerCaptureResults = new ArrayDeque<TotalCaptureResult>( + NUM_IMAGES_TO_BURST); + private ArrayDeque<TotalCaptureResult> mMonoCaptureResults = new ArrayDeque<TotalCaptureResult>( + NUM_IMAGES_TO_BURST); + private ArrayDeque<Image> mBayerImages = new ArrayDeque<Image>( + NUM_IMAGES_TO_BURST); + private ArrayDeque<Image> mMonoImages = new ArrayDeque<Image>( + NUM_IMAGES_TO_BURST); + private int[] mNumImagesToProcess = new int[MAX_NUM_CAM]; + + public ImageProcessHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_CAPTURE: + mNumImagesToProcess[msg.arg1] = msg.arg2; + break; + case MSG_NEW_IMG: + processNewImg(msg); + break; + case MSG_NEW_RESULT: + processNewCaptureResult(msg); + break; + case MSG_SAVE: + processSaveImage(msg); + break; + } + } + + private void processNewImg(Message msg) { + Image image = (Image) msg.obj; + + ArrayDeque<Image> imageQueue; + ArrayDeque<TotalCaptureResult> resultQueue; + ArrayDeque<ReprocessableImage> reprocQueue; + // push image onto queue + if (msg.arg1 == BAYER_ID) { + imageQueue = mBayerImages; + resultQueue = mBayerCaptureResults; + reprocQueue = mBayerFrames; + } else { + imageQueue = mMonoImages; + resultQueue = mMonoCaptureResults; + reprocQueue = mMonoFrames; + } + + imageQueue.add(image); + + Log.d(TAG, "processNewImg - cam: " + msg.arg1 + " num imgs: " + + imageQueue.size() + " num results: " + resultQueue.size()); + if (imageQueue.isEmpty() == resultQueue.isEmpty()) { + Image headImage = imageQueue.poll(); + TotalCaptureResult headResult = resultQueue.poll(); + reprocQueue.add(new ReprocessableImage(headImage, headResult)); + checkForValidFramePair(); + mNumImagesToProcess[msg.arg1]--; + if (mNumImagesToProcess[BAYER_ID] == 0 + && mNumImagesToProcess[MONO_ID] == 0) { + ClearSightNativeEngine.getInstance().reset(); + processReprocess(); + } + } + } + + private void processNewCaptureResult(Message msg) { + if (msg.arg2 == 1) { + // capture failed + mNumImagesToProcess[msg.arg1]--; + } else { + TotalCaptureResult result = (TotalCaptureResult) msg.obj; + ArrayDeque<Image> imageQueue; + ArrayDeque<TotalCaptureResult> resultQueue; + ArrayDeque<ReprocessableImage> reprocQueue; + // push image onto queue + if (msg.arg1 == BAYER_ID) { + imageQueue = mBayerImages; + resultQueue = mBayerCaptureResults; + reprocQueue = mBayerFrames; + } else { + imageQueue = mMonoImages; + resultQueue = mMonoCaptureResults; + reprocQueue = mMonoFrames; + } + + resultQueue.add(result); + + Log.d(TAG, "processNewCaptureResult - cam: " + msg.arg1 + + " num imgs: " + imageQueue.size() + " num results: " + + resultQueue.size()); + if (imageQueue.isEmpty() == resultQueue.isEmpty()) { + Image headImage = imageQueue.poll(); + TotalCaptureResult headResult = resultQueue.poll(); + reprocQueue.add(new ReprocessableImage(headImage, + headResult)); + checkForValidFramePair(); + mNumImagesToProcess[msg.arg1]--; + if (mNumImagesToProcess[BAYER_ID] == 0 + && mNumImagesToProcess[MONO_ID] == 0) { + ClearSightNativeEngine.getInstance().reset(); + processReprocess(); + } + } + } + } + + private void checkForValidFramePair() { + // if we have images from both + // as we just added an image onto one of the queues + // this condition is only true when both are not empty + Log.d(TAG, + "checkForValidFramePair - num bayer frames: " + + mBayerFrames.size() + " num mono frames: " + + mMonoFrames.size()); + + if (mBayerFrames.isEmpty() == mMonoFrames.isEmpty()) { + // peek oldest pair of images + ReprocessableImage bayer = mBayerFrames.peek(); + ReprocessableImage mono = mMonoFrames.peek(); + + Log.d(TAG, + "checkForValidFramePair - bayer ts: " + + bayer.mImage.getTimestamp() + " mono ts: " + + mono.mImage.getTimestamp()); + Log.d(TAG, + "checkForValidFramePair - difference: " + + Math.abs(bayer.mImage.getTimestamp() + - mono.mImage.getTimestamp())); + // if timestamps are within threshold, keep frames + if (Math.abs(bayer.mImage.getTimestamp() + - mono.mImage.getTimestamp()) > TIMESTAMP_THRESHOLD_NS) { + Log.d(TAG, "checkForValidFramePair - toss pair"); + // no match, toss + bayer = mBayerFrames.poll(); + mono = mMonoFrames.poll(); + bayer.mImage.close(); + mono.mImage.close(); + } + } + } + + private void releaseBayerFrames() { + for (ReprocessableImage reprocImg : mBayerFrames) { + reprocImg.mImage.close(); + } + + mBayerFrames.clear(); + } + + private void releaseMonoFrames() { + for (ReprocessableImage reprocImg : mMonoFrames) { + reprocImg.mImage.close(); + } + + mMonoFrames.clear(); + } + + private void processReprocess() { + if (mBayerFrames.size() != mMonoFrames.size() + || mBayerFrames.isEmpty()) { + Log.d(TAG, "processReprocess - frame size mismatch or empty"); + releaseBayerFrames(); + releaseMonoFrames(); + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + RotateTextToast.makeText(mActivity, R.string.clearsight_capture_fail, + Toast.LENGTH_SHORT).show(); + unlockFocus(BAYER_ID); + unlockFocus(MONO_ID); + } + }); + return; + } else { + sendReprocessRequests(BAYER_ID); + sendReprocessRequests(MONO_ID); + } + } + + private void sendReprocessRequests(final int camId) { + try { + ArrayDeque<ReprocessableImage> frameQueue; + if (camId == BAYER_ID) { + frameQueue = mBayerFrames; + } else { + frameQueue = mMonoFrames; + } + Log.d(TAG, "sendReprocessRequests - start cam: " + camId + + " num frames: " + frameQueue.size()); + + ArrayList<CaptureRequest> reprocRequests = new ArrayList<CaptureRequest>( + frameQueue.size()); + while (!frameQueue.isEmpty()) { + ReprocessableImage reprocImg = frameQueue.poll(); + + CaptureRequest.Builder reprocRequest = mCameraDevice[camId] + .createReprocessCaptureRequest(reprocImg.mCaptureResult); + reprocRequest.addTarget(mReprocessImageReader[camId] + .getSurface()); + reprocRequests.add(reprocRequest.build()); + + mImageWriter[camId].queueInputImage(reprocImg.mImage); + } + + mImageReprocessHandler.obtainMessage(MSG_START_CAPTURE, camId, + reprocRequests.size()).sendToTarget(); + mCaptureSession[camId].captureBurst(reprocRequests, + new CameraCaptureCallback(camId) { + @Override + public void onCaptureCompleted( + CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + super.onCaptureCompleted(session, request, result); + Log.d(TAG, "reprocess - onCaptureCompleted: " + + mCamId); + // TODO: parse OTP Calib data to be used in final CS + // result.get(OTP_CALIB_BLOB); + } + + @Override + public void onCaptureFailed( + CameraCaptureSession session, + CaptureRequest request, + CaptureFailure failure) { + super.onCaptureFailed(session, request, failure); + Log.d(TAG, "reprocess - onCaptureFailed: " + + mCamId); + mImageReprocessHandler.obtainMessage( + MSG_NEW_RESULT, mCamId, 1) + .sendToTarget(); + } + }, mCameraHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + private void processSaveImage(Message msg) { + Image image = (Image) msg.obj; + if (image.getFormat() == ImageFormat.JPEG) { + mCaptureStartTime = System.currentTimeMillis(); + mNamedImages.nameNewImage(mCaptureStartTime); + NamedEntity name = mNamedImages.getNextNameEntity(); + String title = (name == null) ? null : name.title; + long date = (name == null) ? -1 : name.date; + + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + + mActivity.getMediaSaveService().addImage(bytes, title, date, + null, image.getWidth(), image.getHeight(), 0, null, + mOnMediaSavedListener, mContentResolver, "jpeg"); + } else { + Log.w(TAG, "processSaveImage - image format incorrect: " + image.getFormat()); + } + image.close(); + } + }; + + private class ImageReprocessHandler extends Handler { + private int[] mNumImagesToProcess = new int[MAX_NUM_CAM]; + + public ImageReprocessHandler(Looper looper) { + super(looper); + } @Override - public void onImageAvailable(ImageReader reader) { + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_CAPTURE: + mNumImagesToProcess[msg.arg1] = msg.arg2; + break; + case MSG_NEW_IMG: + processNewImg(msg); + break; + case MSG_NEW_RESULT: + processNewCaptureResult(msg); + break; + } + } + + private void processNewImg(Message msg) { + Image image = (Image) msg.obj; + boolean isBayer = (msg.arg1 == BAYER_ID); + + Log.d(TAG, "reprocess - processNewImg"); + if (!ClearSightNativeEngine.getInstance() + .hasReferenceImage(isBayer)) { + // reference not yet set + ClearSightNativeEngine.getInstance().setReferenceImage(isBayer, + image); + } else { + // if ref images set, register this image + ClearSightNativeEngine.getInstance().registerImage(isBayer, + image); + } + + mNumImagesToProcess[msg.arg1]--; + + Log.d(TAG, "reprocess - processNewImg, cam: " + msg.arg1 + + " count: " + mNumImagesToProcess[msg.arg1]); + + if (mNumImagesToProcess[BAYER_ID] == 0 + && mNumImagesToProcess[MONO_ID] == 0) { + processClearSight(); + } + } + + private void processNewCaptureResult(Message msg) { + if (msg.arg2 == 1) { + // capture failed + mNumImagesToProcess[msg.arg1]--; + } + + Log.d(TAG, "reprocess - processNewCaptureResult, cam: " + msg.arg1 + + " count: " + mNumImagesToProcess[msg.arg1]); + + if (mNumImagesToProcess[BAYER_ID] == 0 + && mNumImagesToProcess[MONO_ID] == 0) { + processClearSight(); + } + } + + private void processClearSight() { + Log.d(TAG, "reprocess - processClearSight, bayercount: " + + mNumImagesToProcess[BAYER_ID] + " mono count: " + + mNumImagesToProcess[MONO_ID]); + mCaptureStartTime = System.currentTimeMillis(); mNamedImages.nameNewImage(mCaptureStartTime); NamedEntity name = mNamedImages.getNextNameEntity(); String title = (name == null) ? null : name.title; long date = (name == null) ? -1 : name.date; - Image mImage = reader.acquireNextImage(); - ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes); - - mActivity.getMediaSaveService().addImage( - bytes, title, date, null, reader.getWidth(), reader.getHeight(), - 0, null, mOnMediaSavedListener, mContentResolver, "jpeg"); + ClearSightNativeEngine.ClearsightImage csImage = ClearSightNativeEngine + .getInstance().processImage(); + if (csImage != null) { + Log.d(TAG, "reprocess - processClearSight success"); + + mActivity.getMediaSaveService().addMpoImage( + csImage, + createYuvImage(ClearSightNativeEngine.getInstance() + .getReferenceImage(true)), + createYuvImage(ClearSightNativeEngine.getInstance() + .getReferenceImage(false)), null, null, title, + date, null, 0, mOnMediaSavedListener, mContentResolver, + "jpeg"); + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + RotateTextToast.makeText(mActivity, R.string.clearsight_capture_success, + Toast.LENGTH_SHORT).show(); + } + }); + } else { + Log.d(TAG, "reprocess - processClearSight fail"); + Image bayerRef = ClearSightNativeEngine.getInstance() + .getReferenceImage(true); + Image monoRef = ClearSightNativeEngine.getInstance() + .getReferenceImage(false); + if (bayerRef != null && monoRef != null) { + Log.d(TAG, "reprocess - saving with bayer + mono mpo"); + mActivity.getMediaSaveService().addMpoImage(null, + createYuvImage(bayerRef), createYuvImage(monoRef), + null, null, title, date, null, 0, + mOnMediaSavedListener, mContentResolver, "jpeg"); + } else { + Log.d(TAG, "reprocess - bayer + mono images not available"); + } } + unlockFocus(BAYER_ID); + unlockFocus(MONO_ID); + ClearSightNativeEngine.getInstance().reset(); + } }; + private void saveDebugImage(byte[] data, int width, int height, + boolean isReproc) { + mCaptureStartTime = System.currentTimeMillis(); + mNamedImages.nameNewImage(mCaptureStartTime); + NamedEntity name = mNamedImages.getNextNameEntity(); + String title = (name == null) ? null : name.title; + long date = (name == null) ? -1 : name.date; + + if (isReproc) { + title += "_reproc"; + } + + mActivity.getMediaSaveService().addImage(data, title, date, null, + width, height, 0, null, mOnMediaSavedListener, + mContentResolver, "jpeg"); + } + + private void saveDebugImage(YuvImage image, boolean isReproc) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + image.compressToJpeg( + new Rect(0, 0, image.getWidth(), image.getHeight()), 100, baos); + + saveDebugImage(baos.toByteArray(), image.getWidth(), image.getHeight(), + isReproc); + } + + private void saveDebugImage(Image image, boolean isReproc) { + saveDebugImage(createYuvImage(image), isReproc); + } + + private YuvImage createYuvImage(Image image) { + if (image == null) { + Log.d(TAG, "createYuvImage - invalid param"); + return null; + } + Plane[] planes = image.getPlanes(); + ByteBuffer yBuffer = planes[0].getBuffer(); + ByteBuffer vuBuffer = planes[2].getBuffer(); + int sizeY = yBuffer.capacity(); + int sizeVU = vuBuffer.capacity(); + byte[] data = new byte[sizeY + sizeVU]; + yBuffer.rewind(); + yBuffer.get(data, 0, sizeY); + vuBuffer.rewind(); + vuBuffer.get(data, sizeY, sizeVU); + int[] strides = new int[] { planes[0].getRowStride(), + planes[2].getRowStride() }; + + return new YuvImage(data, ImageFormat.NV21, image.getWidth(), + image.getHeight(), strides); + } + /** * {@link CaptureRequest.Builder} for the camera preview */ - private CaptureRequest.Builder[] mPreviewRequestBuilder = new CaptureRequest.Builder[NUMCAM]; - /** - * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} - */ - private CaptureRequest[] mPreviewRequest = new CaptureRequest[NUMCAM]; + private CaptureRequest.Builder[] mPreviewRequestBuilder = new CaptureRequest.Builder[MAX_NUM_CAM]; /** * The current state of camera state for taking pictures. * * @see #mCaptureCallback */ - private int mState = STATE_PREVIEW; + private int[] mState = new int[MAX_NUM_CAM]; /** * A {@link Semaphore} make sure the camera open callback happens first before closing the * camera. @@ -218,23 +702,28 @@ public class CaptureModule implements CameraModule, PhotoController { = new CameraCaptureSession.CaptureCallback() { private void process(CaptureResult result) { - switch (mState) { + int id = (int) result.getRequest().getTag(); + + switch (mState[id]) { case STATE_PREVIEW: { - // We have nothing to do when the camera preview is working normally. break; } case STATE_WAITING_LOCK: { Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Log.d(TAG, "STATE_WAITING_LOCK afState:" + afState + " aeState:" + aeState); + // AF_PASSIVE is added for continous auto focus mode if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || - CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { + CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState || + CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState || + CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED == afState) { // CONTROL_AE_STATE can be null on some devices - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - if (aeState == null || - aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { - mState = STATE_PICTURE_TAKEN; - captureStillPicture(0); + if (aeState == null || (aeState == CaptureResult + .CONTROL_AE_STATE_CONVERGED) && isFlashOff()) { + mState[id] = STATE_PICTURE_TAKEN; + captureStillPicture(id); } else { - runPrecaptureSequence(0); + runPrecaptureSequence(id); } } break; @@ -245,7 +734,7 @@ public class CaptureModule implements CameraModule, PhotoController { if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { - mState = STATE_WAITING_NON_PRECAPTURE; + mState[id] = STATE_WAITING_NON_PRECAPTURE; } break; } @@ -253,8 +742,8 @@ public class CaptureModule implements CameraModule, PhotoController { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { - mState = STATE_PICTURE_TAKEN; - captureStillPicture(0); + mState[id] = STATE_PICTURE_TAKEN; + captureStillPicture(id); } break; } @@ -265,6 +754,8 @@ public class CaptureModule implements CameraModule, PhotoController { public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) { + int id = (int) partialResult.getRequest().getTag(); + if (id == getMainCameraId()) updateFocusStateChange(partialResult); process(partialResult); } @@ -272,63 +763,209 @@ public class CaptureModule implements CameraModule, PhotoController { public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { + int id = (int) result.getRequest().getTag(); + if (id == getMainCameraId()) updateFocusStateChange(result); process(result); } - }; - private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { + private final CameraPreference.OnPreferenceChangedListener prefListener = new + CameraPreference.OnPreferenceChangedListener() { + @Override + public void onSharedPreferenceChanged(ListPreference pref) { + if (mPaused) return; + if (CameraSettings.KEY_CAMERA_SAVEPATH.equals(pref.getKey())) { + Storage.setSaveSDCard( + mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0") + .equals("1")); + mActivity.updateStorageSpaceAndHint(); + } - @Override - public void onOpened(CameraDevice cameraDevice) { - int id = Integer.parseInt(cameraDevice.getId()); + switch (MODE) { + case BAYER_MODE: + applyPreference(0, pref); + break; + case MONO_MODE: + applyPreference(1, pref); + break; + case DUAL_MODE: + applyPreference(0, pref); + applyPreference(1, pref); + } + mUI.overrideSettings(pref.getKey(), null); + } - mCameraOpenCloseLock.release(); + @Override + public void onSharedPreferenceChanged() { + if (mPaused) return; + boolean recordLocation = RecordLocationPreference.get( + mPreferences, mContentResolver); + mLocationManager.recordLocation(recordLocation); + } - PreferenceInflater inflater = new PreferenceInflater(mActivity); - PreferenceGroup group = - (PreferenceGroup) inflater.inflate(R.xml.camera_preferences); - mPreferenceGroup = group; - mActivity.runOnUiThread(new Runnable() { @Override - public void run() { - mUI.onCameraOpened(mPreferenceGroup, prefListener); + public void onRestorePreferencesClicked() { } + @Override + public void onOverriddenPreferencesClicked() { + } - }); + @Override + public void onCameraPickerClicked(int cameraId) { + } + }; + private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { + + @Override + public void onOpened(CameraDevice cameraDevice) { + int id = Integer.parseInt(cameraDevice.getId()); + Log.d(TAG, "onOpened " + id); + mCameraOpenCloseLock.release(); + if (mPaused) { + return; + } + if (MODE == DUAL_MODE && id == BAYER_ID) { + Message msg = Message.obtain(); + msg.what = OPEN_CAMERA; + msg.arg1 = MONO_ID; + mCameraHandler.sendMessage(msg); + } + if (!mInitialized) { + mInitialized = true; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mUI.onCameraOpened(mCharacteristics, mCharacteristicsIndex, + mPreferenceGroup, prefListener); + } + }); + } mCameraDevice[id] = cameraDevice; - mCameraOpened = true; + mCameraOpened[id] = true; createSession(id); } @Override public void onDisconnected(CameraDevice cameraDevice) { - mCameraOpenCloseLock.release(); + int id = Integer.parseInt(cameraDevice.getId()); + Log.d(TAG, "onDisconnected " + id); cameraDevice.close(); mCameraDevice = null; + mCameraOpenCloseLock.release(); } @Override public void onError(CameraDevice cameraDevice, int error) { int id = Integer.parseInt(cameraDevice.getId()); - mCameraOpenCloseLock.release(); + Log.d(TAG, "onError " + id + error); cameraDevice.close(); - mCameraDevice = null; + mCameraDevice[id] = null; + mCameraOpenCloseLock.release(); if (null != mActivity) { mActivity.finish(); } } + @Override + public void onClosed(CameraDevice cameraDevice) { + int id = Integer.parseInt(cameraDevice.getId()); + Log.d(TAG, "onClosed " + id); + mCameraDevice[id] = null; + mCameraOpenCloseLock.release(); + } + }; + public static boolean setMode(String value) { + int mode = DUAL_MODE; + switch (value) { + case "dual": + mode = DUAL_MODE; + ClearSightNativeEngine.createInstance(); + break; + case "bayer": + mode = BAYER_MODE; + break; + case "mono": + mode = MONO_MODE; + break; + } + if (MODE == mode) return false; + MODE = mode; + return true; + } + + public static int getQualityNumber(String jpegQuality) { + try { + int qualityPercentile = Integer.parseInt(jpegQuality); + if (qualityPercentile >= 0 && qualityPercentile <= 100) + return qualityPercentile; + else + return 85; + } catch (NumberFormatException nfe) { + //chosen quality is not a number, continue + } + int value = 0; + switch (jpegQuality) { + case "superfine": + value = CameraProfile.QUALITY_HIGH; + break; + case "fine": + value = CameraProfile.QUALITY_MEDIUM; + break; + case "normal": + value = CameraProfile.QUALITY_LOW; + break; + default: + return 85; + } + return CameraProfile.getJpegEncodingQualityParameter(value); + } + + private void initializeFirstTime() { + if (mFirstTimeInitialized || mPaused) { + return; + } + + //Todo: test record location. Jack to provide instructions + // Initialize location service. + boolean recordLocation = RecordLocationPreference.get( + mPreferences, mContentResolver); + mLocationManager.recordLocation(recordLocation); + + mUI.initializeFirstTime(); + MediaSaveService s = mActivity.getMediaSaveService(); + // We set the listener only when both service and shutterbutton + // are initialized. + if (s != null) { + s.setListener(this); + } + + mNamedImages = new NamedImages(); + mFirstTimeInitialized = true; + } + + private void initializeSecondTime() { + // Start location update if needed. + boolean recordLocation = RecordLocationPreference.get( + mPreferences, mContentResolver); + mLocationManager.recordLocation(recordLocation); + MediaSaveService s = mActivity.getMediaSaveService(); + if (s != null) { + s.setListener(this); + } + mNamedImages = new NamedImages(); + mUI.initializeSecondTime(); + } + private void createSession(final int id) { - if (!mCameraOpened || !mSurfaceReady) return; + if (mPaused || !mCameraOpened[id] || !mSurfaceReady) return; List<Surface> list = new LinkedList<Surface>(); mUI.hidePreviewCover(); try { Surface surface; - if (id == 0) { + if (id == BAYER_ID || (id == MONO_ID && MODE == MONO_MODE)) { SurfaceHolder sh = mUI.getSurfaceHolder(); if (sh == null) { return; @@ -344,139 +981,292 @@ public class CaptureModule implements CameraModule, PhotoController { // We set up a CaptureRequest.Builder with the output Surface. mPreviewRequestBuilder[id] = mCameraDevice[id].createCaptureRequest(CameraDevice .TEMPLATE_PREVIEW); + mPreviewRequestBuilder[id].setTag(id); mPreviewRequestBuilder[id].addTarget(surface); - // This is the CaptureRequest.Builder that we use to take a picture. - mCaptureBuilder[id] = - mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - mCaptureBuilder[id].addTarget(mImageReader[id].getSurface()); - - // Use the same AE and AF modes as the preview. - mCaptureBuilder[id].set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); - mCaptureBuilder[id].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - - list.add(surface); - list.add(mImageReader[id].getSurface()); - // Here, we create a CameraCaptureSession for camera preview. - mCameraDevice[id].createCaptureSession(list, + CameraCaptureSession.StateCallback captureSessionCallback = new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == mCameraDevice[id]) { - return; - } - // When the session is ready, we start displaying the preview. - mCaptureSession[id] = cameraCaptureSession; - try { - // Auto focus should be continuous for camera preview. - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); - // Flash is automatically enabled when necessary. - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - - // Finally, we start displaying the camera preview. - mPreviewRequest[id] = mPreviewRequestBuilder[id].build(); - mCaptureSession[id].setRepeatingRequest(mPreviewRequest[id], - mCaptureCallback, mCameraHandler); - applyAllSettings(0); - } catch (CameraAccessException e) { - e.printStackTrace(); - } + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (mPaused || null == mCameraDevice[id]) { + return; + } + // When the session is ready, we start displaying the preview. + mCaptureSession[id] = cameraCaptureSession; + initializePreviewConfiguration(id); + try { + if (MODE == DUAL_MODE) { + linkBayerMono(id); + mIsLinked = true; } + // Finally, we start displaying the camera preview. + mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id] + .build(), mCaptureCallback, mCameraHandler); - @Override - public void onConfigureFailed( - CameraCaptureSession cameraCaptureSession) { + // For Clearsight + if(mCaptureSession[id].isReprocessable()) { + mImageWriter[id] = ImageWriter.newInstance(cameraCaptureSession.getInputSurface(), NUM_IMAGES_TO_BURST); } - }, null - ); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Log.d(TAG, "cameracapturesession - onConfigureFailed"); + } + + @Override + public void onClosed(CameraCaptureSession session) { + Log.d(TAG, "cameracapturesession - onClosed"); + } + }; + + list.add(surface); + list.add(mImageReader[id].getSurface()); + + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT); + if(pref.getValue().equals(mActivity.getString(R.string.pref_camera_clearsight_value_on))) { + list.add(mReprocessImageReader[id].getSurface()); + // Here, we create a CameraCaptureSession for camera preview. + mCameraDevice[id].createReprocessableCaptureSession( + new InputConfiguration(mImageReader[id].getWidth(), + mImageReader[id].getHeight(), mImageReader[id].getImageFormat()), + list, captureSessionCallback, null); + } else { + // Here, we create a CameraCaptureSession for camera preview. + mCameraDevice[id].createCaptureSession(list, captureSessionCallback, null); + } } catch (CameraAccessException e) { } } @Override public void init(CameraActivity activity, View parent) { - mCameraOpened = false; + Log.d(TAG, "init"); + for (int i = 0; i < MAX_NUM_CAM; i++) { + mCameraOpened[i] = false; + } mSurfaceReady = false; + mActivity = activity; + for (int i = 0; i < MAX_NUM_CAM; i++) { + mState[i] = STATE_PREVIEW; + } mPreferences = new ComboPreferences(mActivity); CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal(), activity); - mPreferences.setLocalId(mActivity, 0); + mPreferences.setLocalId(mActivity, BAYER_ID); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); + PreferenceInflater inflater = new PreferenceInflater(mActivity); + PreferenceGroup group = + (PreferenceGroup) inflater.inflate(R.xml.camera_preferences); + mPreferenceGroup = group; + + ListPreference pref = group.findPreference(CameraSettings.KEY_DUAL_CAMERA); + setMode(pref.getValue()); + mContentResolver = mActivity.getContentResolver(); mUI = new CaptureUI(activity, this, parent); mUI.initializeControlByIntent(); - mNamedImages = new NamedImages(); + mFocusStateListener = new FocusStateListener(mUI); + mLocationManager = new LocationManager(mActivity, mUI); + Storage.setSaveSDCard( + mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0").equals("1")); } /** * Initiate a still image capture. */ private void takePicture() { - lockFocus(0); + Log.d(TAG, "takePicture"); + switch (MODE) { + case DUAL_MODE: + lockFocus(BAYER_ID); + lockFocus(MONO_ID); + break; + case BAYER_MODE: + lockFocus(BAYER_ID); + break; + case MONO_MODE: + lockFocus(MONO_ID); + break; + } } /** * Lock the focus as the first step for a still image capture. */ private void lockFocus(int id) { + Log.d(TAG, "lockFocus " + id); try { - // This is how to tell the camera to lock focus. - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER, - CameraMetadata.CONTROL_AF_TRIGGER_START); - // Tell #mCaptureCallback to wait for the lock. - mState = STATE_WAITING_LOCK; - mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback, - mCameraHandler); + CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice + .TEMPLATE_PREVIEW); + builder.setTag(id); + builder.addTarget(getPreviewSurface(id)); + + builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + applyWhiteBalance(builder); + applyZoom(builder, id); + mState[id] = STATE_WAITING_LOCK; + mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } + private void autoFocusTrigger(int id) { + Log.d(TAG, "autoFocusTrigger " + id); + try { + CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice + .TEMPLATE_PREVIEW); + builder.setTag(id); + builder.addTarget(getPreviewSurface(id)); + + builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + applyWhiteBalance(builder); + applyZoom(builder, id); + applyAFRegions(builder, id); + mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + public void linkBayerMono(int id) { + Log.d(TAG, "linkBayerMono " + id); + if (id == BAYER_ID) { + mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 1); + mPreviewRequestBuilder[id].set(BayerMonoLinkMainKey, (byte) 1); + mPreviewRequestBuilder[id].set(BayerMonoLinkSessionIdKey, MONO_ID); + } else if (id == MONO_ID) { + mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 1); + mPreviewRequestBuilder[id].set(BayerMonoLinkMainKey, (byte) 0); + mPreviewRequestBuilder[id].set(BayerMonoLinkSessionIdKey, BAYER_ID); + } + } + + public void unLinkBayerMono(int id) { + Log.d(TAG, "unlinkBayerMono " + id); + if (id == BAYER_ID) { + mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 0); + } else if (id == MONO_ID) { + mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 0); + } + } + + /** * Capture a still picture. This method should be called when we get a response in * {@link #mCaptureCallback} from both {@link #lockFocus()}. */ private void captureStillPicture(final int id) { + Log.d(TAG, "captureStillPicture " + id); try { - if (null == mActivity || null == mCameraDevice) { + if (null == mActivity || null == mCameraDevice[id]) { return; } - // Orientation - int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - mCaptureBuilder[id].set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - List<CaptureRequest> burstList = new ArrayList<CaptureRequest>(); + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT); + final boolean csEnabled = pref.getValue().equals( + mActivity.getString(R.string.pref_camera_clearsight_value_on)); + CaptureRequest.Builder captureBuilder; + CameraCaptureSession.CaptureCallback captureCallback; + + if(csEnabled) { + captureBuilder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG); + + // Orientation + // int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + // captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); + + captureCallback = new CameraCaptureSession.CaptureCallback() { + + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + Log.d(TAG, "captureStillPicture onCaptureCompleted: " + id); + result.dumpToLog(); + mImageProcessHandler.obtainMessage(MSG_NEW_RESULT, + id, 0, result).sendToTarget(); + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, + CaptureRequest request, + CaptureFailure result) { + Log.d(TAG, "captureStillPicture onCaptureFailed: " + id); + mImageProcessHandler.obtainMessage(MSG_NEW_RESULT, + id, 1, result).sendToTarget(); + } - for (int i = 0; i < 1; i++) { - burstList.add(mCaptureBuilder[id].build()); + @Override + public void onCaptureSequenceCompleted(CameraCaptureSession session, int + sequenceId, long frameNumber) { + Log.d(TAG, "captureStillPicture onCaptureSequenceCompleted: " + id); + } + }; + } else { + // No Clearsight + captureBuilder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + + // Orientation + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); + captureCallback = new CameraCaptureSession.CaptureCallback() { + + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + Log.d(TAG, "captureStillPicture onCaptureCompleted: " + id); + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, + CaptureRequest request, + CaptureFailure result) { + Log.d(TAG, "captureStillPicture onCaptureFailed: " + id); + } + + @Override + public void onCaptureSequenceCompleted(CameraCaptureSession session, int + sequenceId, long frameNumber) { + Log.d(TAG, "captureStillPicture onCaptureSequenceCompleted: " + id); + unlockFocus(id); + } + }; } - CameraCaptureSession.CaptureCallback CaptureCallback - = new CameraCaptureSession.CaptureCallback() { + captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + captureBuilder.addTarget(getPreviewSurface(id)); + captureBuilder.addTarget(mImageReader[id].getSurface()); + captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + applyCaptureSettings(captureBuilder, id); - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - } + mCaptureSession[id].stopRepeating(); - @Override - public void onCaptureSequenceCompleted(CameraCaptureSession session, int - sequenceId, long frameNumber) { - unlockFocus(id); + if(csEnabled) { + List<CaptureRequest> burstList = new ArrayList<CaptureRequest>(); + for (int i = 0; i < NUM_IMAGES_TO_BURST; i++) { + burstList.add(captureBuilder.build()); } - - }; - mCaptureSession[id].captureBurst(burstList, CaptureCallback, mCallbackHandler); + mImageProcessHandler.obtainMessage(MSG_START_CAPTURE, id, burstList.size()).sendToTarget(); + mCaptureSession[id].captureBurst(burstList, captureCallback, mCallbackHandler); + } else { + mCaptureSession[id].capture(captureBuilder.build(), captureCallback, mCallbackHandler); + } } catch (CameraAccessException e) { Log.d(TAG, "Capture still picture has failed"); e.printStackTrace(); @@ -488,14 +1278,21 @@ public class CaptureModule implements CameraModule, PhotoController { * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. */ private void runPrecaptureSequence(int id) { + Log.d(TAG, "runPrecaptureSequence"); try { - // This is how to tell the camera to trigger. - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice + .TEMPLATE_PREVIEW); + builder.setTag(id); + builder.addTarget(getPreviewSurface(id)); + builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); - // Tell #mCaptureCallback to wait for the precapture sequence to be set. - mState = STATE_WAITING_PRECAPTURE; - mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback, - mCameraHandler); + // Applying flash only to capture does not work. Need to apply flash here. + applyFlash(builder); + applyWhiteBalance(builder); + applyZoom(builder, id); + mState[id] = STATE_WAITING_PRECAPTURE; + mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -508,33 +1305,74 @@ public class CaptureModule implements CameraModule, PhotoController { * @param height The height of available size for camera preview */ private void setUpCameraOutputs() { - Activity activity = mActivity; - CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); try { String[] cameraIdList = manager.getCameraIdList(); - for (int i = 0; i < NUMCAM; i++) { + + for (int i = 0; i < cameraIdList.length; i++) { String cameraId = cameraIdList[i]; CameraCharacteristics characteristics - = manager.getCameraCharacteristics(cameraId); - + = manager.getCameraCharacteristics(cameraId); + mCharacteristics[i] = characteristics; + mCharacteristicsIndex.add(i); StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { continue; } + Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + Log.d(TAG, "flash : " + (available == null ? false : available)); + mCameraId[i] = cameraId; - // For still image captures, we use the largest available size. - Size largest = Collections.max( - Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), - new CompareSizesByArea()); + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT); + if(pref.getValue().equals(mActivity.getString(R.string.pref_camera_clearsight_value_on))) { + // For still image captures, we use the largest available size. + Size largest = Collections.max( + Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), + new CompareSizesByArea()); - mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), - ImageFormat.JPEG, 10); - mImageReader[i].setOnImageAvailableListener( - mOnImageAvailableListener, mImageAvailableHandler); - mCameraId[i] = cameraId; - //return; + mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.YUV_420_888, NUM_IMAGES_TO_BURST); + mImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) { + @Override + public void onImageAvailable(ImageReader reader) { + Log.d(TAG, "image available for cam: " + mCamId); + mImageProcessHandler.obtainMessage( + MSG_NEW_IMG, mCamId, 0, reader.acquireNextImage()).sendToTarget(); + } + }, mImageAvailableHandler); + + mReprocessImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.YUV_420_888, NUM_IMAGES_TO_BURST); + mReprocessImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) { + @Override + public void onImageAvailable(ImageReader reader) { + Log.d(TAG, "reprocessed image available for cam: " + mCamId); + mImageReprocessHandler.obtainMessage( + MSG_NEW_IMG, mCamId, 0, reader.acquireNextImage()).sendToTarget(); + } + }, mImageAvailableHandler); + } else { + // No Clearsight + // For still image captures, we use the largest available size. + Size largest = Collections.max( + Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), + new CompareSizesByArea()); + + mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.JPEG, 3); + mImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) { + @Override + public void onImageAvailable(ImageReader reader) { + Log.d(TAG, "image available for cam: " + mCamId); + mImageProcessHandler.obtainMessage( + MSG_SAVE, mCamId, 0, reader.acquireNextImage()).sendToTarget(); + } + }, mImageAvailableHandler); + } } + mAutoFocusSupported = CameraUtil.isAutoFocusSupported(mCharacteristics, + mCharacteristicsIndex); } catch (CameraAccessException e) { e.printStackTrace(); } catch (NullPointerException e) { @@ -546,45 +1384,81 @@ public class CaptureModule implements CameraModule, PhotoController { * finished. */ private void unlockFocus(int id) { + Log.d(TAG, "unlockFocus " + id); try { - // Reset the auto-focus trigger - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER, - CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); - mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback, - mCameraHandler); - // After this, the camera will go back to the normal state of preview. - mState = STATE_PREVIEW; - mCaptureSession[id].setRepeatingRequest(mPreviewRequest[id], mCaptureCallback, - mCameraHandler); + CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice + .TEMPLATE_PREVIEW); + builder.setTag(id); + builder.addTarget(getPreviewSurface(id)); + builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest + .CONTROL_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest + .CONTROL_AF_MODE_AUTO); + builder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + //Todo: Create applyCommonSettings function for settings applied everytime + applyWhiteBalance(builder); + applyZoom(builder, id); + mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler); + mState[id] = STATE_PREVIEW; + mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id].build(), + mCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } + /** * Closes the current {@link CameraDevice}. */ private void closeCamera() { + Log.d(TAG, "closeCamera"); try { mCameraOpenCloseLock.acquire(); - for (int i = 0; i < NUMCAM; i++) { + for (int i = 0; i < MAX_NUM_CAM; i++) { if (null != mCaptureSession[i]) { + if (mIsLinked) { + unLinkBayerMono(i); + try { + mCaptureSession[i].capture(mPreviewRequestBuilder[i].build(), null, + mCameraHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + mCaptureSession[i].close(); mCaptureSession[i] = null; } - if (null != mCameraDevice[i]) { - mCameraDevice[i].close(); - mCameraDevice[i] = null; - mCameraOpened = false; - } if (null != mImageReader[i]) { mImageReader[i].close(); mImageReader[i] = null; } + if (null != mReprocessImageReader[i]) { + mReprocessImageReader[i].close(); + mReprocessImageReader[i] = null; + } + if (null != mImageWriter[i]) { + mImageWriter[i].close(); + mImageWriter[i] = null; + } + } + for (int i = 0; i < MAX_NUM_CAM; i++) { + if (null != mCameraDevice[i]) { + mCameraDevice[i].close(); + mCameraDevice[i] = null; + mCameraOpened[i] = false; + } } + /* no need to set this in the callback and handle asynchronously. This is the same + reason as why we release the semaphore here, not in camera close callback function + as we don't have to protect the case where camera open() gets called during camera + close(). The low level framework/HAL handles the synchronization for open() + happens after close() */ + mIsLinked = false; } catch (InterruptedException e) { + mCameraOpenCloseLock.release(); throw new RuntimeException("Interrupted while trying to lock camera closing.", e); } finally { mCameraOpenCloseLock.release(); @@ -601,10 +1475,16 @@ public class CaptureModule implements CameraModule, PhotoController { mImageAvailableThread.start(); mCallbackThread = new HandlerThread("CameraCallback"); mCallbackThread.start(); + mImageProcessThread = new HandlerThread("CameraImageProcess"); + mImageProcessThread.start(); + mImageReprocessThread = new HandlerThread("CameraImageReprocess"); + mImageReprocessThread.start(); - mCameraHandler = new Handler(mCameraThread.getLooper()); + mCameraHandler = new MyCameraHandler(mCameraThread.getLooper()); mImageAvailableHandler = new Handler(mImageAvailableThread.getLooper()); mCallbackHandler = new Handler(mCallbackThread.getLooper()); + mImageProcessHandler = new ImageProcessHandler(mImageProcessThread.getLooper()); + mImageReprocessHandler = new ImageReprocessHandler(mImageReprocessThread.getLooper()); } /** @@ -614,6 +1494,8 @@ public class CaptureModule implements CameraModule, PhotoController { mCameraThread.quitSafely(); mImageAvailableThread.quitSafely(); mCallbackThread.quitSafely(); + mImageProcessThread.quitSafely(); + mImageReprocessThread.quitSafely(); try { mCameraThread.join(); mCameraThread = null; @@ -635,15 +1517,34 @@ public class CaptureModule implements CameraModule, PhotoController { } catch (InterruptedException e) { e.printStackTrace(); } + try { + mImageProcessThread.join(); + mImageProcessThread = null; + mImageProcessHandler = null; + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { + mImageReprocessThread.join(); + mImageReprocessThread = null; + mImageReprocessHandler = null; + } catch (InterruptedException e) { + e.printStackTrace(); + } } private void openCamera(int id) { + if (mPaused) { + return; + } + Log.d(TAG, "openCamera " + id); CameraManager manager; try { manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); mCameraId[id] = manager.getCameraIdList()[id]; - if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { + if (!mCameraOpenCloseLock.tryAcquire(5000, TimeUnit.MILLISECONDS)) { Log.d(TAG, "Time out waiting to lock camera opening."); + throw new RuntimeException("Time out waiting to lock camera opening"); } manager.openCamera(mCameraId[id], mStateCallback, mCameraHandler); } catch (CameraAccessException e) { @@ -655,35 +1556,57 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void onPreviewFocusChanged(boolean previewFocused) { - + mUI.onPreviewFocusChanged(previewFocused); } @Override public void onPauseBeforeSuper() { - + mPaused = true; } @Override public void onPauseAfterSuper() { + Log.d(TAG, "onPause"); mUI.showPreviewCover(); - stopBackgroundThread(); + if (mLocationManager != null) mLocationManager.recordLocation(false); closeCamera(); + stopBackgroundThread(); mUI.onPause(); } @Override public void onResumeBeforeSuper() { - + mPaused = false; } @Override public void onResumeAfterSuper() { - setUpCameraOutputs(); - openCamera(0); + Log.d(TAG, "onResume " + MODE); + mCharacteristicsIndex = new ArrayList<>(); startBackgroundThread(); + setUpCameraOutputs(); + readInitialValues(); + Message msg = Message.obtain(); + msg.what = OPEN_CAMERA; + switch (MODE) { + case DUAL_MODE: + case BAYER_MODE: + msg.arg1 = BAYER_ID; + mCameraHandler.sendMessage(msg); + break; + case MONO_MODE: + msg.what = OPEN_CAMERA; + msg.arg1 = MONO_ID; + mCameraHandler.sendMessage(msg); + break; + } + if (!mFirstTimeInitialized) { + initializeFirstTime(); + } else { + initializeSecondTime(); + } mUI.hidePreviewCover(); mUI.enableShutter(true); - mUI.initializeFirstTime(); } @Override @@ -708,7 +1631,7 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public boolean onBackPressed() { - return false; + return mUI.onBackPressed(); } @Override @@ -727,13 +1650,31 @@ public class CaptureModule implements CameraModule, PhotoController { } @Override + public void onZoomChanged(float requestedZoom) { + mZoomValue = requestedZoom; + + switch (MODE) { + case DUAL_MODE: + applyZoomAndUpdate(BAYER_ID); + applyZoomAndUpdate(MONO_ID); + break; + case BAYER_MODE: + applyZoomAndUpdate(BAYER_ID); + break; + case MONO_MODE: + applyZoomAndUpdate(MONO_ID); + break; + } + } + + @Override public boolean isImageCaptureIntent() { return false; } @Override public boolean isCameraIdle() { - return false; + return true; } @Override @@ -768,7 +1709,42 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void onSingleTapUp(View view, int x, int y) { + if (mPaused || mCameraDevice == null || !mFirstTimeInitialized || !mAutoFocusSupported || + !isStatePreview()) { + return; + } + mUI.setFocusPosition(x, y); + mUI.onFocusStarted(); + switch (MODE) { + case DUAL_MODE: + triggerFocusAtPoint(x, y, BAYER_ID); + triggerFocusAtPoint(x, y, MONO_ID); + break; + case BAYER_MODE: + triggerFocusAtPoint(x, y, BAYER_ID); + break; + case MONO_MODE: + triggerFocusAtPoint(x, y, MONO_ID); + break; + } + } + private int getMainCameraId() { + switch (MODE) { + case DUAL_MODE: + case BAYER_MODE: + return BAYER_ID; + case MONO_MODE: + return MONO_ID; + } + return 0; + } + + private boolean isStatePreview() { + for (int i = 0; i < mState.length; i++) { + if (mState[i] != STATE_PREVIEW) return false; + } + return true; } @Override @@ -793,13 +1769,37 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void enableRecordingLocation(boolean enable) { + Log.d(TAG, "CaptureModule enableRecordingLocation " + enable); + setLocationPreference(enable ? RecordLocationPreference.VALUE_ON + : RecordLocationPreference.VALUE_OFF); + } + private void setLocationPreference(String value) { + mPreferences.edit() + .putString(CameraSettings.KEY_RECORD_LOCATION, value) + .apply(); + prefListener.onSharedPreferenceChanged(); } @Override public void onPreviewUIReady() { + if (mPaused) { + return; + } + Log.d(TAG, "onPreviewUIReady"); mSurfaceReady = true; - createSession(0); + switch (MODE) { + case DUAL_MODE: + createSession(BAYER_ID); + createSession(MONO_ID); + break; + case BAYER_MODE: + createSession(BAYER_ID); + break; + case MONO_MODE: + createSession(MONO_ID); + break; + } } @Override @@ -829,7 +1829,15 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void onOrientationChanged(int orientation) { - + // We keep the last known orientation. So if the user first orient + // the camera then point the camera to floor or sky, we still have + // the correct orientation. + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; + int oldOrientation = mOrientation; + mOrientation = CameraUtil.roundOrientation(orientation, mOrientation); + if (oldOrientation != mOrientation) { + mUI.setOrientation(mOrientation, true); + } } @Override @@ -839,7 +1847,9 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void onMediaSaveServiceConnected(MediaSaveService s) { - + if (mFirstTimeInitialized) { + s.setListener(this); + } } @Override @@ -854,7 +1864,15 @@ public class CaptureModule implements CameraModule, PhotoController { @Override public void onSwitchSavePath() { - + if (mUI.mMenuInitialized) { + mUI.setPreference(CameraSettings.KEY_CAMERA_SAVEPATH, "1"); + } else { + mPreferences.edit() + .putString(CameraSettings.KEY_CAMERA_SAVEPATH, "1") + .apply(); + } + RotateTextToast.makeText(mActivity, R.string.on_switch_save_path_to_sdcard, + Toast.LENGTH_SHORT).show(); } @Override @@ -872,23 +1890,225 @@ public class CaptureModule implements CameraModule, PhotoController { } + private boolean isFlashOff() { + return readSetting(CameraSettings.KEY_FLASH_MODE).equals("off"); + } + + private void initializePreviewConfiguration(int id) { + mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_MODE, CaptureRequest + .CONTROL_MODE_AUTO); + mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest + .CONTROL_AF_MODE_CONTINUOUS_PICTURE); + mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest + .CONTROL_AF_TRIGGER_IDLE); + mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest + .CONTROL_AE_MODE_ON); + mPreviewRequestBuilder[id].set(CaptureRequest.FLASH_MODE, CaptureRequest + .FLASH_MODE_OFF); + applyWhiteBalance(mPreviewRequestBuilder[id]); + applyZoom(mPreviewRequestBuilder[id], id); + } + + private void readInitialValues() { + for (int i = 0; i < mPreferenceGroup.size(); i++) { + CameraPreference pref = mPreferenceGroup.get(i); + if (pref instanceof ListPreference) { + ListPreference listPref = (ListPreference) pref; + storeSetting(listPref.getKey(), listPref.getValue()); + } + } + } + + public Rect cropRegionForZoom(int id) { + Log.d(TAG, "cropRegionForZoom " + id); + Rect activeRegion = mCharacteristics[id].get(CameraCharacteristics + .SENSOR_INFO_ACTIVE_ARRAY_SIZE); + Rect cropRegion = new Rect(); + + int xCenter = activeRegion.width() / 2; + int yCenter = activeRegion.height() / 2; + int xDelta = (int) (activeRegion.width() / (2 * mZoomValue)); + int yDelta = (int) (activeRegion.height() / (2 * mZoomValue)); + cropRegion.set(xCenter - xDelta, yCenter - yDelta, xCenter + xDelta, yCenter + yDelta); + mCropRegion[id] = cropRegion; + return mCropRegion[id]; + } + + private void applyZoom(CaptureRequest.Builder request, int id) { + request.set(CaptureRequest.SCALER_CROP_REGION, cropRegionForZoom(id)); + } + + private void applyCaptureSettings(CaptureRequest.Builder request, int id) { + applyFlash(request); + applyWhiteBalance(request); + applyJpegQuality(request); + applyZoom(request, id); + } + private void applyPreference(int cameraId, ListPreference pref) { - if (pref.getKey().equals(CameraSettings.KEY_FLASH_MODE)) { - if (pref.getValue().equals("on")) { - mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); - } else if (pref.getValue().equals("auto")) { - mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - } else if (pref.getValue().equals("off")) { - mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE, - CaptureRequest.CONTROL_AE_MODE_ON); + boolean updatePreview = false; + String key = pref.getKey(); + String value = pref.getValue(); + storeSetting(key, value); + switch (key) { + //Todo: CreateUISettings file and add UI preference settings to there + case CameraSettings.KEY_JPEG_QUALITY: + mJpegQuality = getQualityNumber(value); + break; + case CameraSettings.KEY_WHITE_BALANCE: + updatePreview = true; + applyWhiteBalance(mPreviewRequestBuilder[cameraId]); + break; + } + if (updatePreview) { + try { + mCaptureSession[cameraId].setRepeatingRequest(mPreviewRequestBuilder[cameraId] + .build(), mCaptureCallback, mCameraHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); } } } - private void applyAllSettings(int cameraId) { + private void applyZoomAndUpdate(int id) { + applyZoom(mPreviewRequestBuilder[id], id); + try { + mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id] + .build(), mCaptureCallback, mCameraHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + private void storeSetting(String key, String value) { + mSettings.put(key, value); + } + + private String readSetting(String key) { + return mSettings.get(key); + } + + private void applyJpegQuality(CaptureRequest.Builder request) { + request.set(CaptureRequest.JPEG_QUALITY, (byte) mJpegQuality); + } + + private void applyAFRegions(CaptureRequest.Builder request, int id) { + request.set(CaptureRequest.CONTROL_AF_REGIONS, mAFRegions[id]); + } + + private void applyWhiteBalance(CaptureRequest.Builder request) { + String value = readSetting(CameraSettings.KEY_WHITE_BALANCE); + switch (value) { + case "incandescent": + request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest + .CONTROL_AWB_MODE_INCANDESCENT); + break; + case "fluorescent": + request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest + .CONTROL_AWB_MODE_FLUORESCENT); + break; + case "auto": + request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest + .CONTROL_AWB_MODE_AUTO); + break; + case "daylight": + request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest + .CONTROL_AWB_MODE_DAYLIGHT); + break; + case "cloudy-daylight": + request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest + .CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); + break; + } + } + + private Surface getPreviewSurface(int id) { + if (MODE == DUAL_MODE && id == MONO_ID) { + return mUI.getSurfaceHolder2().getSurface(); + } else { + return mUI.getSurfaceHolder().getSurface(); + } + } + + private void applyFlash(CaptureRequest.Builder request, String value) { + switch (value) { + case "on": + request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest + .CONTROL_AE_MODE_ON_ALWAYS_FLASH); + request.set(CaptureRequest.FLASH_MODE, CaptureRequest + .FLASH_MODE_SINGLE); + break; + case "auto": + request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest + .CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case "off": + request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest + .CONTROL_AE_MODE_ON); + request.set(CaptureRequest.FLASH_MODE, CaptureRequest + .FLASH_MODE_OFF); + break; + } + } + + private void applyFlash(CaptureRequest.Builder request) { + String value = readSetting(CameraSettings.KEY_FLASH_MODE); + applyFlash(request, value); + } + + @Override + public void onQueueStatus(boolean full) { + mUI.enableShutter(!full); + } + + public void triggerFocusAtPoint(float x, float y, int id) { + Point p; + if (id == getMainCameraId()) { + p = mUI.getSurfaceViewSize(); + } else { + p = mUI.getSurfaceView2Size(); + } + int width = p.x; + int height = p.y; + x = x / width; + y = y / height; + mAFRegions[id] = afRectangle(x, y, mCropRegion[id]); + autoFocusTrigger(id); + } + private MeteringRectangle[] afRectangle(float x, float y, Rect cropRegion) { + int side = Math.max(cropRegion.width(), cropRegion.height()) / 8; + int xCenter = (int) (cropRegion.left + x * cropRegion.width()); + int yCenter = (int) (cropRegion.top + y * cropRegion.height()); + Rect meteringRegion = new Rect(xCenter - side / 2, yCenter - side / 2, xCenter + + side / 2, yCenter + side / 2); + + meteringRegion.left = CameraUtil.clamp(meteringRegion.left, cropRegion.left, cropRegion + .right); + meteringRegion.top = CameraUtil.clamp(meteringRegion.top, cropRegion.top, cropRegion + .bottom); + meteringRegion.right = CameraUtil.clamp(meteringRegion.right, cropRegion.left, cropRegion + .right); + meteringRegion.bottom = CameraUtil.clamp(meteringRegion.bottom, cropRegion.top, + cropRegion.bottom); + MeteringRectangle[] meteringRectangle = new MeteringRectangle[1]; + meteringRectangle[0] = new MeteringRectangle(meteringRegion, 1); + return meteringRectangle; + } + + private void updateFocusStateChange(CaptureResult result) { + final int resultAFState = result.get(CaptureResult.CONTROL_AF_STATE); + + // Report state change when AF state has changed. + if (resultAFState != mLastResultAFState && mFocusStateListener != null) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mFocusStateListener.onFocusStatusUpdate(resultAFState); + } + }); + } + mLastResultAFState = resultAFState; } /** @@ -904,4 +2124,23 @@ public class CaptureModule implements CameraModule, PhotoController { } } + + private class MyCameraHandler extends Handler { + + public MyCameraHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OPEN_CAMERA: { + int id = msg.arg1; + openCamera(id); + break; + } + } + } + } + } diff --git a/src/com/android/camera/CaptureUI.java b/src/com/android/camera/CaptureUI.java index 9095cd188..a6accae7b 100644 --- a/src/com/android/camera/CaptureUI.java +++ b/src/com/android/camera/CaptureUI.java @@ -24,8 +24,8 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.RectF; import android.graphics.drawable.AnimationDrawable; -import android.hardware.Camera; import android.hardware.Camera.Face; +import android.hardware.camera2.CameraCharacteristics; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; @@ -70,7 +70,7 @@ public class CaptureUI implements PieListener, CameraManager.CameraFaceDetectionCallback { private static final String TAG = "SnapCam_CaptureUI"; - private boolean mMenuInitialized = false; + public boolean mMenuInitialized = false; private boolean surface1created = false; private boolean surface2created = false; private CameraActivity mActivity; @@ -111,8 +111,6 @@ public class CaptureUI implements PieListener, private OnScreenIndicators mOnScreenIndicators; private PieRenderer mPieRenderer; private ZoomRenderer mZoomRenderer; - private int mZoomMax; - private List<Integer> mZoomRatios; private int mPreviewWidth = 0; private int mPreviewHeight = 0; private int mOriginalPreviewWidth = 0; @@ -189,16 +187,12 @@ public class CaptureUI implements PieListener, mPreviewCover = mRootView.findViewById(R.id.preview_cover); // display the view mSurfaceView = (SurfaceView) mRootView.findViewById(R.id.mdp_preview_content); - mSurfaceView.setVisibility(View.VISIBLE); mSurfaceView2 = (SurfaceView) mRootView.findViewById(R.id.mdp_preview_content2); - //mSurfaceView2.setVisibility(View.VISIBLE); + mSurfaceView2.setZOrderMediaOverlay(true); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); - //mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder2 = mSurfaceView2.getHolder(); mSurfaceHolder2.addCallback(callback); - //mSurfaceHolder2.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - mSurfaceView.addOnLayoutChangeListener(mLayoutListener); Log.v(TAG, "Using mdp_preview_content (MDP path)"); mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); @@ -284,7 +278,15 @@ public class CaptureUI implements PieListener, mRootView.findViewById(R.id.on_screen_indicators)); } - public void onCameraOpened(PreferenceGroup prefGroup, OnPreferenceChangedListener listener) { + public void onCameraOpened(CameraCharacteristics[] characteristics, + List<Integer> characteristicsIndex, PreferenceGroup prefGroup, + OnPreferenceChangedListener listener) { + if (mPieRenderer == null) { + mPieRenderer = new PieRenderer(mActivity); + mPieRenderer.setPieListener(this); + mRenderOverlay.addRenderer(mPieRenderer); + } + if (mMenu == null) { mMenu = new CaptureMenu(mActivity, this); mMenu.setListener(listener); @@ -304,8 +306,11 @@ public class CaptureUI implements PieListener, } mGestures.setCaptureMenu(mMenu); + mGestures.setZoomEnabled(CameraUtil.isZoomSupported(characteristics, characteristicsIndex)); mGestures.setRenderOverlay(mRenderOverlay); mRenderOverlay.requestLayout(); + + initializeZoom(characteristics, characteristicsIndex); mActivity.setPreviewGestures(mGestures); } @@ -355,31 +360,32 @@ public class CaptureUI implements PieListener, mShutterButton.setVisibility(View.VISIBLE); } - public void doShutterAnimation() { - AnimationDrawable frameAnimation = (AnimationDrawable) mShutterButton.getDrawable(); - frameAnimation.stop(); - frameAnimation.start(); - } - // called from onResume every other time - public void initializeSecondTime(Camera.Parameters params) { - initializeZoom(params); + public void initializeSecondTime() { if (mMenu != null) { mMenu.reloadPreferences(); } } - public void initializeZoom(Camera.Parameters params) { - if ((params == null) || !params.isZoomSupported() - || (mZoomRenderer == null)) return; - mZoomMax = params.getMaxZoom(); - mZoomRatios = params.getZoomRatios(); - // Currently we use immediate zoom for fast zooming to get better UX and - // there is no plan to take advantage of the smooth zoom. + public void doShutterAnimation() { + AnimationDrawable frameAnimation = (AnimationDrawable) mShutterButton.getDrawable(); + frameAnimation.stop(); + frameAnimation.start(); + } + + public void initializeZoom(CameraCharacteristics[] characteristics, + List<Integer> characteristicsIndex) { + if ((characteristics == null) || !CameraUtil.isZoomSupported(characteristics, + characteristicsIndex) || (mZoomRenderer == null)) + return; if (mZoomRenderer != null) { - mZoomRenderer.setZoomMax(mZoomMax); - mZoomRenderer.setZoom(params.getZoom()); - mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom())); + float zoomMax = Float.MAX_VALUE; + for (int i = 0; i < characteristicsIndex.size(); i++) { + zoomMax = Math.min(characteristics[characteristicsIndex.get(i)].get + (CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM), zoomMax); + } + mZoomRenderer.setZoomMax(zoomMax); + mZoomRenderer.setZoom(1f); mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); } } @@ -671,6 +677,14 @@ public class CaptureUI implements PieListener, } } + public void hideSurfaceView() { + mSurfaceView.setVisibility(View.INVISIBLE); + } + + public void showSurfaceView() { + mSurfaceView.setVisibility(View.VISIBLE); + } + public void onPause() { // Clear UI. collapseCameraControls(); @@ -678,7 +692,7 @@ public class CaptureUI implements PieListener, // focus UI implementation private FocusIndicator getFocusIndicator() { - return null; + return mPieRenderer; } @Override @@ -689,6 +703,10 @@ public class CaptureUI implements PieListener, public void clearFaces() { } + public void setPreference(String key, String value) { + mMenu.setPreference(key, value); + } + @Override public void clearFocus() { FocusIndicator indicator = getFocusIndicator(); @@ -768,16 +786,28 @@ public class CaptureUI implements PieListener, } } + public Point getSurfaceViewSize() { + Point point = new Point(); + if (mSurfaceView != null) point.set(mSurfaceView.getWidth(), mSurfaceView.getHeight()); + return point; + } + + public Point getSurfaceView2Size() { + Point point = new Point(); + if (mSurfaceView2 != null) point.set(mSurfaceView2.getWidth(), mSurfaceView2.getHeight()); + return point; + } + public int getOrientation() { return mOrientation; } private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { @Override - public void onZoomValueChanged(int index) { - int newZoom = mController.onZoomChanged(index); + public void onZoomValueChanged(float mZoomValue) { + mController.onZoomChanged(mZoomValue); if (mZoomRenderer != null) { - mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); + mZoomRenderer.setZoom(mZoomValue); } } @@ -795,6 +825,11 @@ public class CaptureUI implements PieListener, mPieRenderer.setBlockFocus(false); } } + + @Override + public void onZoomValueChanged(int index) { + + } } } diff --git a/src/com/android/camera/FocusStateListener.java b/src/com/android/camera/FocusStateListener.java new file mode 100644 index 000000000..42edd5c34 --- /dev/null +++ b/src/com/android/camera/FocusStateListener.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.camera; + +import android.hardware.camera2.CaptureResult; + +public class FocusStateListener { + private CaptureUI mUI; + + public FocusStateListener(CaptureUI ui) { + mUI = ui; + } + + public void onFocusStatusUpdate(int focusState) { + switch (focusState) { + case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN: + mUI.onFocusStarted(); + break; + case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: + mUI.onFocusSucceeded(true); + break; + case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: + mUI.onFocusFailed(true); + break; + case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: + mUI.onFocusSucceeded(true); + break; + case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: + mUI.onFocusStarted(); + break; + case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: + mUI.onFocusFailed(true); + break; + case CaptureResult.CONTROL_AF_STATE_INACTIVE: + mUI.clearFocus(); + break; + } + } +} diff --git a/src/com/android/camera/MediaSaveService.java b/src/com/android/camera/MediaSaveService.java index 46abe0560..8570b16fd 100644 --- a/src/com/android/camera/MediaSaveService.java +++ b/src/com/android/camera/MediaSaveService.java @@ -16,11 +16,18 @@ package com.android.camera; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.ByteOrder; + import android.app.Service; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.graphics.BitmapFactory; +import android.graphics.Rect; +import android.graphics.YuvImage; +import android.hardware.camera2.TotalCaptureResult; import android.location.Location; import android.net.Uri; import android.os.AsyncTask; @@ -32,6 +39,10 @@ import com.android.camera.PhotoModule; import com.android.camera.exif.ExifInterface; import java.io.File; +import com.android.camera.mpo.MpoData; +import com.android.camera.mpo.MpoImageData; +import com.android.camera.mpo.MpoInterface; +import com.android.camera.util.ClearSightNativeEngine.ClearsightImage; /* * Service for saving images in the background thread. @@ -87,6 +98,31 @@ public class MediaSaveService extends Service { return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT); } + public void addMpoImage(final ClearsightImage csImage, + final YuvImage bayerImg, final YuvImage monoImg, + TotalCaptureResult bayerResult, TotalCaptureResult monoResult, + String title, long date, Location loc, int orientation, + OnMediaSavedListener l, ContentResolver resolver, + String pictureFormat) { + if (isQueueFull()) { + Log.e(TAG, "Cannot add image when the queue is full"); + return; + } + + MpoSaveTask t = new MpoSaveTask(csImage, bayerImg, monoImg, + bayerResult, monoResult, title, date, loc, orientation, l, + resolver, pictureFormat); + + long size = (csImage == null ? 0 + : csImage.getDataLength()) + + bayerImg.getYuvData().length + monoImg.getYuvData().length; + mMemoryUse += size; + if (isQueueFull()) { + onQueueFull(); + } + t.execute(); + } + public void addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver, String pictureFormat) { @@ -141,6 +177,97 @@ public class MediaSaveService extends Service { if (mListener != null) mListener.onQueueStatus(false); } + private class MpoSaveTask extends AsyncTask<Void, Void, Uri> { + private ClearsightImage csImage; + private YuvImage bayerImage; + private YuvImage monoImage; + private String title; + private long date; + private Location loc; + private int width, height; + private int orientation; + private TotalCaptureResult bayerResult; + private TotalCaptureResult monoResult; + private ContentResolver resolver; + private OnMediaSavedListener listener; + private String pictureFormat; + + public MpoSaveTask(ClearsightImage csImage, YuvImage bayerImg, + YuvImage monoImg, TotalCaptureResult bayerResult, + TotalCaptureResult monoResult, String title, long date, + Location loc, int orientation, OnMediaSavedListener listener, + ContentResolver resolver, String pictureFormat) { + this.csImage = csImage; + this.bayerImage = bayerImg; + this.monoImage = monoImg; + this.title = title; + this.date = date; + this.loc = loc; + this.width = bayerImg.getWidth(); + this.height = bayerImg.getHeight(); + this.orientation = orientation; + this.bayerResult = bayerResult; + this.monoResult = monoResult; + this.resolver = resolver; + this.listener = listener; + this.pictureFormat = pictureFormat; + } + + @Override + protected Uri doInBackground(Void... v) { + // encode jpeg and add exif for all images + MpoData mpo = new MpoData(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bayerImage.compressToJpeg(new Rect(0, 0, bayerImage.getWidth(), + bayerImage.getHeight()), 100, baos); + MpoImageData bayer = new MpoImageData(baos.toByteArray(), + ByteOrder.BIG_ENDIAN); + + baos.reset(); + monoImage.compressToJpeg(new Rect(0, 0, monoImage.getWidth(), + monoImage.getHeight()), 100, baos); + MpoImageData mono = new MpoImageData(baos.toByteArray(), + ByteOrder.BIG_ENDIAN); + + if (csImage == null) { + mpo.addAuxiliaryMpoImage(mono); + mpo.setPrimaryMpoImage(bayer); + } else { + MpoImageData cs = new MpoImageData(csImage.compressToJpeg(), + ByteOrder.BIG_ENDIAN); + + mpo.addAuxiliaryMpoImage(bayer); + mpo.addAuxiliaryMpoImage(mono); + mpo.setPrimaryMpoImage(cs); + } + + // combine to single mpo + String path = Storage.generateFilepath(title, pictureFormat); + int size = MpoInterface.writeMpo(mpo, path); + // Try to get the real image size after add exif. + File f = new File(path); + if (f.exists() && f.isFile()) { + size = (int) f.length(); + } + return Storage.addImage(resolver, title, date, loc, orientation, + size, path, width, height, pictureFormat); + } + + @Override + protected void onPostExecute(Uri uri) { + if (listener != null) + listener.onMediaSaved(uri); + boolean previouslyFull = isQueueFull(); + long size = (csImage == null ? 0 + : csImage.getDataLength()) + + bayerImage.getYuvData().length + + monoImage.getYuvData().length; + mMemoryUse -= size; + if (isQueueFull() != previouslyFull) + onQueueAvailable(); + } + } + private class ImageSaveTask extends AsyncTask <Void, Void, Uri> { private byte[] data; private String title; diff --git a/src/com/android/camera/PhotoController.java b/src/com/android/camera/PhotoController.java index b08f8c088..887f0d375 100644 --- a/src/com/android/camera/PhotoController.java +++ b/src/com/android/camera/PhotoController.java @@ -37,6 +37,8 @@ public interface PhotoController extends OnShutterButtonListener { // returns the actual set zoom value public int onZoomChanged(int requestedZoom); + public void onZoomChanged(float requestedZoom); + public boolean isImageCaptureIntent(); public boolean isCameraIdle(); diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java index b3c6117fa..b9ecdcc8d 100644 --- a/src/com/android/camera/PhotoMenu.java +++ b/src/com/android/camera/PhotoMenu.java @@ -16,6 +16,7 @@ package com.android.camera; +import java.util.HashSet; import java.util.Locale; import android.animation.Animator; @@ -117,6 +118,7 @@ public class PhotoMenu extends MenuController private MakeupLevelListener mMakeupListener; private MakeupHandler mHandler = new MakeupHandler(); private static final int MAKEUP_MESSAGE_ID = 0; + private HashSet<View> mWasVisibleSet = new HashSet<View>(); public PhotoMenu(CameraActivity activity, PhotoUI ui, MakeupLevelListener makeupListener) { super(activity); @@ -1509,7 +1511,17 @@ public class PhotoMenu extends MenuController } mSceneModeSwitcher.setVisibility(status); mFilterModeSwitcher.setVisibility(status); - mCameraSwitcher.setVisibility(status); + if(status == View.INVISIBLE) { + if(mCameraSwitcher.getVisibility() == View.VISIBLE) { + mWasVisibleSet.add(mCameraSwitcher); + } + mCameraSwitcher.setVisibility(status); + } else { + if(mWasVisibleSet.contains(mCameraSwitcher)) { + mCameraSwitcher.setVisibility(status); + mWasVisibleSet.remove(mCameraSwitcher); + } + } mPreviewThumbnail.setVisibility(status); } } diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 589f55152..4d2ec6ad8 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -696,6 +696,7 @@ public class PhotoModule private void switchCamera() { if (mPaused) return; + mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.HIDE); Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId); mCameraId = mPendingSwitchCameraId; mPendingSwitchCameraId = -1; @@ -735,6 +736,7 @@ public class PhotoModule mFocusManager.setParameters(mInitialParams); setupPreview(); + mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.SURFACE_VIEW); // reset zoom value index mZoomValue = 0; resizeForPreviewAspectRatio(); @@ -1126,23 +1128,26 @@ public class PhotoModule for (int i =0;i<3;i++) { metadata[i] = byteToInt( (byte []) data, i*4); } - if (metadata[2] == 1) { - mAutoHdrEnable = true; - mActivity.runOnUiThread(new Runnable() { - public void run() { - if (mDrawAutoHDR != null) - mDrawAutoHDR.AutoHDR(); - } - }); - } - else { - mAutoHdrEnable = false; - mActivity.runOnUiThread(new Runnable() { - public void run() { - if (mDrawAutoHDR != null) - mDrawAutoHDR.AutoHDR(); - } - }); + /* Checking if the meta data is for auto HDR */ + if (metadata[0] == 3) { + if (metadata[2] == 1) { + mAutoHdrEnable = true; + mActivity.runOnUiThread(new Runnable() { + public void run() { + if (mDrawAutoHDR != null) + mDrawAutoHDR.AutoHDR(); + } + }); + } + else { + mAutoHdrEnable = false; + mActivity.runOnUiThread(new Runnable() { + public void run() { + if (mDrawAutoHDR != null) + mDrawAutoHDR.AutoHDR(); + } + }); + } } } } @@ -2364,6 +2369,7 @@ public class PhotoModule @Override public void onResumeAfterSuper() { mLastPhotoTakenWithRefocus = false; + mUI.showSurfaceView(); // Add delay on resume from lock screen only, in order to to speed up // the onResume --> onPause --> onResume cycle from lock screen. // Don't do always because letting go of thread can cause delay. @@ -2372,17 +2378,17 @@ public class PhotoModule || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { Log.v(TAG, "On resume, from lock screen."); + // Check if there is a need to take a snapshot without + // waiting for the shutter click + if (isInstantCaptureEnabled()) { + mInstantCaptureSnapShot = true; + } + // Note: onPauseAfterSuper() will delete this runnable, so we will // at most have 1 copy queued up. mHandler.postDelayed(new Runnable() { public void run() { onResumeTasks(); - - // Check if there is a need to take a snapshot without - // waiting for the shutter click - if (isInstantCaptureEnabled()) { - mInstantCaptureSnapShot = true; - } } }, ON_RESUME_TASKS_DELAY_MSEC); } else { @@ -2425,6 +2431,8 @@ public class PhotoModule mOpenCameraThread.start(); } + mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.SURFACE_VIEW); + mJpegPictureCallbackTime = 0; mZoomValue = 0; @@ -2463,6 +2471,8 @@ public class PhotoModule @Override public void onPauseBeforeSuper() { mPaused = true; + mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.HIDE); + Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (gsensor != null) { mSensorManager.unregisterListener(this, gsensor); @@ -2491,6 +2501,7 @@ public class PhotoModule public void onPauseAfterSuper() { Log.v(TAG, "On pause."); mUI.showPreviewCover(); + mUI.hideSurfaceView(); try { if (mOpenCameraThread != null) { @@ -2564,12 +2575,6 @@ public class PhotoModule // we will update focus manager with proper UI. if (mFocusManager != null && mUI != null) { mFocusManager.setPhotoUI(mUI); - - View root = mUI.getRootView(); - // These depend on camera parameters. - int width = root.getWidth(); - int height = root.getHeight(); - mFocusManager.setPreviewSize(width, height); } } @@ -4657,6 +4662,11 @@ public class PhotoModule } @Override + public void onZoomChanged(float requestedZoom) { + + } + + @Override public int getCameraState() { return mCameraState; } @@ -4976,7 +4986,7 @@ class DrawAutoHDR extends View{ AutoHDRPaint.setStyle(Paint.Style.STROKE); AutoHDRPaint.setColor(Color.MAGENTA); AutoHDRPaint.setStrokeWidth(1); - AutoHDRPaint.setTextSize(16); + AutoHDRPaint.setTextSize(32); AutoHDRPaint.setAlpha (255); canvas.drawText("HDR On",200,100,AutoHDRPaint); } diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index 7e3659f0f..c8e01fea0 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -160,26 +160,15 @@ public class PhotoUI implements PieListener, private int mOrientation; private float mScreenBrightness = 0.0f; + public enum SURFACE_STATUS { + HIDE, + SURFACE_VIEW; + } + public interface SurfaceTextureSizeChangedListener { public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight); } - private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - tryToCloseSubList(); - - Camera.Parameters parameters = ((PhotoModule)mController).getParameters(); - if(parameters != null) { - Camera.Size size = parameters.getPreviewSize(); - if (size != null) { - setAspectRatio((float) size.width / size.height); - } - } - } - }; - public CameraControls getCameraControls() { return mCameraControls; } @@ -233,6 +222,14 @@ public class PhotoUI implements PieListener, } } + public synchronized void applySurfaceChange(SURFACE_STATUS status) { + if(status == SURFACE_STATUS.HIDE) { + mSurfaceView.setVisibility(View.GONE); + return; + } + mSurfaceView.setVisibility(View.VISIBLE); + } + public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { mActivity = activity; mController = controller; @@ -246,11 +243,8 @@ public class PhotoUI implements PieListener, mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - mSurfaceView.addOnLayoutChangeListener(mLayoutListener); Log.v(TAG, "Using mdp_preview_content (MDP path)"); - - View surfaceContainer = mRootView.findViewById(R.id.preview_container); - surfaceContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { + mSurfaceView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, @@ -258,26 +252,13 @@ public class PhotoUI implements PieListener, int width = right - left; int height = bottom - top; + tryToCloseSubList(); + if (mMaxPreviewWidth == 0 && mMaxPreviewHeight == 0) { mMaxPreviewWidth = width; mMaxPreviewHeight = height; } - int orientation = mActivity.getResources().getConfiguration().orientation; - if ((orientation == Configuration.ORIENTATION_PORTRAIT && width > height) - || (orientation == Configuration.ORIENTATION_LANDSCAPE && width < height)) { - // The screen has rotated; swap SurfaceView width & height - // to ensure correct preview - int oldWidth = width; - width = height; - height = oldWidth; - Log.d(TAG, "Swapping SurfaceView width & height dimensions"); - if (mMaxPreviewWidth != 0 && mMaxPreviewHeight != 0) { - int temp = mMaxPreviewWidth; - mMaxPreviewWidth = mMaxPreviewHeight; - mMaxPreviewHeight = temp; - } - } if (mOrientationResize != mPrevOrientationResize || mAspectRatioResize) { layoutPreview(mAspectRatio); @@ -478,9 +459,6 @@ public class PhotoUI implements PieListener, if (mFaceView != null) { mFaceView.setLayoutParams(lp); } - - mController.onScreenSizeChanged((int) mSurfaceTextureUncroppedWidth, - (int) mSurfaceTextureUncroppedHeight); } public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) { @@ -597,6 +575,7 @@ public class PhotoUI implements PieListener, }); if (mController.isImageCaptureIntent()) { hideSwitcher(); + mCameraControls.hideRemainingPhotoCnt(); mSwitcher.setSwitcherVisibility(false); ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls); mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); @@ -1148,6 +1127,11 @@ public class PhotoUI implements PieListener, mPieRenderer.setBlockFocus(false); } } + + @Override + public void onZoomValueChanged(float value) { + + } } @Override @@ -1176,6 +1160,13 @@ public class PhotoUI implements PieListener, return mSurfaceHolder; } + public void hideSurfaceView() { + mSurfaceView.setVisibility(View.INVISIBLE); + } + + public void showSurfaceView() { + mSurfaceView.setVisibility(View.VISIBLE); + } // Countdown timer private void initializeCountDown() { diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java index 00f7d02b5..4f26240ea 100644 --- a/src/com/android/camera/PreviewGestures.java +++ b/src/com/android/camera/PreviewGestures.java @@ -107,8 +107,9 @@ public class PreviewGestures else if (mCaptureMenu != null && !mCaptureMenu.isMenuBeingShown()) mCaptureMenu.openFirstLevel(); return true; + } else { + return onSingleTapUp(e2); } - return false; } private boolean isLeftSwipe(int orientation, int deltaX, int deltaY) { diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java index 302dd47f0..670572774 100644 --- a/src/com/android/camera/VideoMenu.java +++ b/src/com/android/camera/VideoMenu.java @@ -729,8 +729,7 @@ public class VideoMenu extends MenuController mPrevSavedVideoCDS = cds; } - if ((tnr != null) && !mActivity.getString(R.string. - pref_camera_video_tnr_default).equals(tnr)) { + if ((tnr != null) && !tnr.equals("off")) { mListMenu.setPreferenceEnabled( CameraSettings.KEY_VIDEO_CDS_MODE,false); mListMenu.overrideSettings( diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 4724bd203..b2175d7b2 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -315,7 +315,7 @@ public class VideoModule implements CameraModule, VIDEO_ENCODER_TABLE.put("h263", MediaRecorder.VideoEncoder.H263); VIDEO_ENCODER_TABLE.put("h264", MediaRecorder.VideoEncoder.H264); - VIDEO_ENCODER_TABLE.put("h265", MediaRecorder.VideoEncoder.H265); + // VIDEO_ENCODER_TABLE.put("h265", MediaRecorder.VideoEncoder.H265); VIDEO_ENCODER_TABLE.put("m4v", MediaRecorder.VideoEncoder.MPEG_4_SP); VIDEO_ENCODER_TABLE.putDefault(MediaRecorder.VideoEncoder.DEFAULT); @@ -903,7 +903,8 @@ public class VideoModule implements CameraModule, public boolean is4KEnabled() { if (mProfile.quality == CamcorderProfile.QUALITY_2160P || - mProfile.quality == CamcorderProfile.QUALITY_4KDCI) { + mProfile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_2160P || + mProfile.quality == CamcorderProfile.QUALITY_4KDCI ) { return true; } else { return false; @@ -1094,6 +1095,8 @@ public class VideoModule implements CameraModule, mUI.enableShutter(true); } + mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.SURFACE_VIEW); + mUI.initDisplayChangeListener(); // Initializing it here after the preview is started. mUI.initializeZoom(mParameters); @@ -1279,6 +1282,7 @@ public class VideoModule implements CameraModule, if(mWasMute != mIsMute) { setMute(mWasMute, false); } + mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.HIDE); } @Override @@ -1657,12 +1661,13 @@ public class VideoModule implements CameraModule, long duration = 0L; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - retriever.setDataSource(mCurrentVideoFilename); + try { + retriever.setDataSource(mCurrentVideoFilename); duration = Long.valueOf(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_DURATION)); - } catch (NumberFormatException e) { - Log.e(TAG, "cannot retrieve duration metadata"); + } catch (IllegalArgumentException e) { + Log.e(TAG, "cannot access the file"); } retriever.release(); @@ -2716,6 +2721,7 @@ public class VideoModule implements CameraModule, } Log.d(TAG, "Start to switch camera."); + mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.HIDE); mCameraId = mPendingSwitchCameraId; mPendingSwitchCameraId = -1; setCameraId(mCameraId); @@ -2727,6 +2733,7 @@ public class VideoModule implements CameraModule, CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); openCamera(); readVideoPreferences(); + mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.SURFACE_VIEW); startPreview(); initializeVideoSnapshot(); resizeForPreviewAspectRatio(); diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index 9a882e946..c99c8c8f5 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -130,13 +130,10 @@ public class VideoUI implements PieRenderer.PieListener, private float mSurfaceTextureUncroppedWidth; private float mSurfaceTextureUncroppedHeight; - private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - tryToCloseSubList(); - } - }; + public enum SURFACE_STATUS { + HIDE, + SURFACE_VIEW; + } public void showPreviewCover() { mPreviewCover.setVisibility(View.VISIBLE); @@ -177,6 +174,14 @@ public class VideoUI implements PieRenderer.PieListener, } } + public synchronized void applySurfaceChange(SURFACE_STATUS status) { + if(status == SURFACE_STATUS.HIDE) { + mSurfaceView.setVisibility(View.GONE); + return; + } + mSurfaceView.setVisibility(View.VISIBLE); + } + public VideoUI(CameraActivity activity, VideoController controller, View parent) { mActivity = activity; mController = controller; @@ -190,9 +195,7 @@ public class VideoUI implements PieRenderer.PieListener, mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - mSurfaceView.addOnLayoutChangeListener(mLayoutListener); Log.v(TAG, "Using mdp_preview_content (MDP path)"); - View surfaceContainer = mRootView.findViewById(R.id.preview_container); surfaceContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override @@ -202,6 +205,7 @@ public class VideoUI implements PieRenderer.PieListener, int width = right - left; int height = bottom - top; + tryToCloseSubList(); if (mMaxPreviewWidth == 0 && mMaxPreviewHeight == 0) { mMaxPreviewWidth = width; mMaxPreviewHeight = height; @@ -1108,14 +1112,17 @@ public class VideoUI implements PieRenderer.PieListener, mPieRenderer.setBlockFocus(false); } } + + @Override + public void onZoomValueChanged(float value) { + + } } // SurfaceHolder callbacks @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.v(TAG, "surfaceChanged: width = " + width + ", height = " + height); - // Make sure preview cover is hidden if preview data is available. - hidePreviewCover(); } @Override diff --git a/src/com/android/camera/exif/ExifTag.java b/src/com/android/camera/exif/ExifTag.java index 1d50316dd..fcf0f5c29 100644 --- a/src/com/android/camera/exif/ExifTag.java +++ b/src/com/android/camera/exif/ExifTag.java @@ -85,7 +85,7 @@ public class ExifTag { TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; } - static final int SIZE_UNDEFINED = 0; + public static final int SIZE_UNDEFINED = 0; // Exif TagId private final short mTagId; @@ -124,7 +124,7 @@ public class ExifTag { } // Use builtTag in ExifInterface instead of constructor. - ExifTag(short tagId, short type, int componentCount, int ifd, + public ExifTag(short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount) { mTagId = tagId; mDataType = type; @@ -163,7 +163,7 @@ public class ExifTag { return mIfd; } - protected void setIfd(int ifdId) { + public void setIfd(int ifdId) { mIfd = ifdId; } @@ -785,7 +785,7 @@ public class ExifTag { * @exception IllegalArgumentException if the data type is * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. */ - protected long getValueAt(int index) { + public long getValueAt(int index) { if (mValue instanceof long[]) { return ((long[]) mValue)[index]; } else if (mValue instanceof byte[]) { @@ -812,7 +812,7 @@ public class ExifTag { /* * Get the converted ascii byte. Used by ExifOutputStream. */ - protected byte[] getStringByte() { + public byte[] getStringByte() { return (byte[]) mValue; } @@ -822,7 +822,7 @@ public class ExifTag { * @exception IllegalArgumentException If the type is NOT * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. */ - protected Rational getRational(int index) { + public Rational getRational(int index) { if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) { throw new IllegalArgumentException("Cannot get RATIONAL value from " + convertTypeToString(mDataType)); @@ -833,7 +833,7 @@ public class ExifTag { /** * Equivalent to getBytes(buffer, 0, buffer.length). */ - protected void getBytes(byte[] buf) { + public void getBytes(byte[] buf) { getBytes(buf, 0, buf.length); } @@ -847,7 +847,7 @@ public class ExifTag { * @exception IllegalArgumentException If the type is NOT * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. */ - protected void getBytes(byte[] buf, int offset, int length) { + public void getBytes(byte[] buf, int offset, int length) { if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) { throw new IllegalArgumentException("Cannot get BYTE value from " + convertTypeToString(mDataType)); @@ -860,14 +860,14 @@ public class ExifTag { * Gets the offset of this tag. This is only valid if this data size > 4 and * contains an offset to the location of the actual value. */ - protected int getOffset() { + public int getOffset() { return mOffset; } /** * Sets the offset of this tag. */ - protected void setOffset(int offset) { + public void setOffset(int offset) { mOffset = offset; } diff --git a/src/com/android/camera/exif/JpegHeader.java b/src/com/android/camera/exif/JpegHeader.java index 383617af4..6fc46ca18 100644 --- a/src/com/android/camera/exif/JpegHeader.java +++ b/src/com/android/camera/exif/JpegHeader.java @@ -16,8 +16,9 @@ package com.android.camera.exif; -class JpegHeader { +public class JpegHeader { public static final short SOI = (short) 0xFFD8; + public static final short APP2 = (short) 0xFFE2; public static final short APP1 = (short) 0xFFE1; public static final short APP0 = (short) 0xFFE0; public static final short EOI = (short) 0xFFD9; diff --git a/src/com/android/camera/exif/OrderedDataOutputStream.java b/src/com/android/camera/exif/OrderedDataOutputStream.java index abc0a6eb1..3206d72b0 100644 --- a/src/com/android/camera/exif/OrderedDataOutputStream.java +++ b/src/com/android/camera/exif/OrderedDataOutputStream.java @@ -22,7 +22,7 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -class OrderedDataOutputStream extends FilterOutputStream { +public class OrderedDataOutputStream extends FilterOutputStream { private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4); private int mSize = 0; diff --git a/src/com/android/camera/mpo/MpoData.java b/src/com/android/camera/mpo/MpoData.java new file mode 100644 index 000000000..7cd431ebe --- /dev/null +++ b/src/com/android/camera/mpo/MpoData.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.camera.mpo; + +import java.util.ArrayList; +import java.util.List; + +import com.android.camera.mpo.MpoTag.MpEntry; + +public class MpoData { + + private MpoImageData mPrimaryMpoImage; + private ArrayList<MpoImageData> mAuxiliaryImages = new ArrayList<MpoImageData>(); + + public MpoData() { + } + + public void setPrimaryMpoImage(MpoImageData image) { + mPrimaryMpoImage = image; + addDefaultAttribIfdTags(mPrimaryMpoImage, 1); + addDefaultIndexIfdTags(); + } + + public void addAuxiliaryMpoImage(MpoImageData image) { + mAuxiliaryImages.add(image); + int imageNum = getAuxiliaryImageCount() + ((mPrimaryMpoImage == null) ? 0 : 1); + addDefaultAttribIfdTags(image, imageNum); + } + + public boolean removeAuxiliaryMpoImage(MpoImageData image) { + boolean ret = mAuxiliaryImages.remove(image); + return ret; + } + + public MpoImageData getPrimaryMpoImage() { + return mPrimaryMpoImage; + } + + public List<MpoImageData> getAuxiliaryMpoImages() { + return mAuxiliaryImages; + } + + public int getAuxiliaryImageCount() { + return mAuxiliaryImages.size(); + } + + public void addDefaultAttribIfdTags(MpoImageData image, int imageNum) { + MpoTag mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION, + MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_ATTRIB_IFD, true); + mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE); + image.addTag(mpFormatVersionTag); + + MpoTag imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER, + MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false); + imageNumTag.setValue(imageNum); + image.addTag(imageNumTag); + } + + public void addDefaultIndexIfdTags() { + if (mPrimaryMpoImage == null) + throw new IllegalArgumentException("Primary Mpo Image has not been set"); + if (getAuxiliaryImageCount() == 0) + throw new IllegalArgumentException("No auxiliary images have been added"); + + MpoTag mpFormatVersionTag = mPrimaryMpoImage.getTag( + (short) MpoInterface.TAG_MP_FORMAT_VERSION, MpoIfdData.TYPE_MP_INDEX_IFD); + if (mpFormatVersionTag == null) { + mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION, + MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_INDEX_IFD, true); + mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE); + mPrimaryMpoImage.addTag(mpFormatVersionTag); + } + + MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES, + MpoIfdData.TYPE_MP_INDEX_IFD); + if (numImagesTag == null) { + numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES, + MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false); + } + numImagesTag.setValue(getAuxiliaryImageCount() + 1); + mPrimaryMpoImage.addTag(numImagesTag); + + // check, create and add required tags + MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED, + MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false); + ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1); + mpEntries.add(new MpEntry()); // primary image + for (int i = 0; i < getAuxiliaryImageCount(); i++) { + mpEntries.add(new MpEntry()); // aux images + } + mpEntryTag.setValue(mpEntries); + mPrimaryMpoImage.addTag(mpEntryTag); + } + + public void updateAllTags() { + updateAttribIfdTags(); + updateIndexIfdTags(); + } + + private void updateIndexIfdTags() { + if (mPrimaryMpoImage == null) + throw new IllegalArgumentException("Primary Mpo Image has not been set"); + if (getAuxiliaryImageCount() == 0) + throw new IllegalArgumentException("No auxiliary images have been added"); + + MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES, + MpoIfdData.TYPE_MP_INDEX_IFD); + if (numImagesTag == null) { + numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES, + MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false); + } + numImagesTag.setValue(getAuxiliaryImageCount() + 1); + mPrimaryMpoImage.addTag(numImagesTag); + + // check, create and add required tags + MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED, + MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false); + ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1); + + int imgOffset = 0; + // primary image + MpEntry entry = new MpEntry(1 << 29, mPrimaryMpoImage.calculateImageSize(), imgOffset); + mpEntries.add(entry); + imgOffset += mPrimaryMpoImage.calculateImageSize(); + + for (MpoImageData image : getAuxiliaryMpoImages()) { + int imageSize = image.calculateImageSize(); + entry = new MpEntry(0x020002, imageSize, imgOffset); + mpEntries.add(entry); // aux images + imgOffset += imageSize; + } + mpEntryTag.setValue(mpEntries); + mPrimaryMpoImage.addTag(mpEntryTag); + } + + private void updateAttribIfdTags() { + if (mPrimaryMpoImage == null) + throw new IllegalArgumentException("Primary Mpo Image has not been set"); + if (getAuxiliaryImageCount() == 0) + throw new IllegalArgumentException("No auxiliary images have been added"); + + int imageNum = 1; + MpoTag imageNumTag = null; + + imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER, MpoTag.TYPE_UNSIGNED_LONG, + 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false); + imageNumTag.setValue(0xFFFFFFFFL); + mPrimaryMpoImage.addTag(imageNumTag); + + for (MpoImageData image : getAuxiliaryMpoImages()) { + imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER, + MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false); + imageNumTag.setValue(imageNum++); + image.addTag(imageNumTag); + } + } +}
\ No newline at end of file diff --git a/src/com/android/camera/mpo/MpoIfdData.java b/src/com/android/camera/mpo/MpoIfdData.java new file mode 100644 index 000000000..8e422944a --- /dev/null +++ b/src/com/android/camera/mpo/MpoIfdData.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a contribution. + * + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.mpo; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class stores all the tags in an MP Index IFD. + */ +public class MpoIfdData { + public static final int TYPE_MP_INDEX_IFD = 1; + public static final int TYPE_MP_ATTRIB_IFD = 2; + public static final byte[] MP_FORMAT_VER_VALUE = { 0x30, 0x31, 0x30, 0x30 }; + + private final int mIfdId; + private final Map<Short, MpoTag> mTags = new HashMap<Short, MpoTag>(); + private int mOffsetToNextIfd = 0; + + /** + * Creates an empty MpIndexIfdData + */ + public MpoIfdData(int ifdId) { + mIfdId = ifdId; + } + + /** + * Get a array the contains all {@link MpoTag} in this IFD. + */ + protected MpoTag[] getAllTags() { + return mTags.values().toArray(new MpoTag[mTags.size()]); + } + + /** + * Gets the {@link MpoTag} with given tag id. Return null if there is no + * such tag. + */ + protected MpoTag getTag(short tagId) { + return mTags.get(tagId); + } + + /** + * Adds or replaces a {@link MpoTag}. + */ + protected MpoTag setTag(MpoTag tag) { + tag.setIfd(mIfdId); + return mTags.put(tag.getTagId(), tag); + } + + protected boolean checkCollision(short tagId) { + return mTags.get(tagId) != null; + } + + /** + * Removes the tag of the given ID + */ + protected void removeTag(short tagId) { + mTags.remove(tagId); + } + + /** + * Gets the tags count in the IFD. + */ + protected int getTagCount() { + return mTags.size(); + } + + /** + * Sets the offset of next IFD. + */ + protected void setOffsetToNextIfd(int offset) { + mOffsetToNextIfd = offset; + } + + /** + * Gets the offset of next IFD. + */ + protected int getOffsetToNextIfd() { + return mOffsetToNextIfd; + } + + /** + * Returns true if all tags in this two IFDs are equal. Note that tags of + * IFD offset will be ignored. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof MpoIfdData) { + MpoIfdData data = (MpoIfdData) obj; + if (data.getTagCount() == getTagCount()) { + MpoTag[] tags = data.getAllTags(); + for (MpoTag tag : tags) { + MpoTag tag2 = mTags.get(tag.getTagId()); + if (!tag.equals(tag2)) { + return false; + } + } + return true; + } + } + return false; + } +} diff --git a/src/com/android/camera/mpo/MpoImageData.java b/src/com/android/camera/mpo/MpoImageData.java new file mode 100644 index 000000000..84d2d0f6e --- /dev/null +++ b/src/com/android/camera/mpo/MpoImageData.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a contribution. + * + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.mpo; + +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * This class stores the MPO header in IFDs according to the MPO specification. + */ + +public class MpoImageData { + private static final String TAG = "MpoImageData"; + static final int OFFSET_TO_FIRST_IFD = 8; + static final int MP_FORMAT_IDENTIFIER = 0x4D504600; // 'M' 'P' 'F' 'NULL' + static final int MP_HEADER_SIZE = 8; + static final int APP_HEADER_SIZE = 6; + + private final MpoIfdData mMpIndexIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_INDEX_IFD); + private final MpoIfdData mMpAttribIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD); + private final byte[] mJpegData; + private final ByteOrder mByteOrder; + + public MpoImageData(byte[] jpegData, ByteOrder byteOrder) { + mJpegData = jpegData; + mByteOrder = byteOrder; + } + + /** + * Gets the jpeg data. + */ + protected byte[] getJpegData() { + return mJpegData; + } + + /** + * Gets the byte order. + */ + protected ByteOrder getByteOrder() { + return mByteOrder; + } + + /** + * Returns the {@link mMpAttribIfdData} object if it exists or null. + */ + protected MpoIfdData getAttribIfdData() { + return mMpAttribIfdData; + } + + /** + * Returns the {@link mMpIndexIfdData} object if it exists or null. + */ + protected MpoIfdData getIndexIfdData() { + return mMpIndexIfdData; + } + + /** + * Returns the {@link MpoIfdData} object corresponding to a given IFD. + */ + protected MpoIfdData getMpIfdData(int ifdId) { + return (ifdId == MpoIfdData.TYPE_MP_INDEX_IFD) ? mMpIndexIfdData : mMpAttribIfdData; + } + + /** + * Returns the tag with a given tag ID in the given IFD if the tag exists. + * Otherwise returns null. + */ + protected MpoTag getTag(short tag, int ifd) { + MpoIfdData mpIfdData = getMpIfdData(ifd); + return mpIfdData.getTag(tag); + } + + /** + * Adds the given MpoTag to its default IFD and returns an existing MpoTag + * with the same TID or null if none exist. + */ + protected MpoTag addTag(MpoTag tag) { + if (tag != null) { + int ifd = tag.getIfd(); + return addTag(tag, ifd); + } + return null; + } + + /** + * Adds the given MpoTag to the given IFD and returns an existing MpoTag + * with the same tag ID or null if none exist. + */ + protected MpoTag addTag(MpoTag tag, int ifdId) { + if (tag != null && MpoTag.isValidIfd(ifdId)) { + return getMpIfdData(ifdId).setTag(tag); + } + return null; + } + + /** + * Removes the tag with a given tag ID and IFD. + */ + protected void removeTag(short tagId, int ifdId) { + getMpIfdData(ifdId).removeTag(tagId); + } + + /** + * Returns a list of all {@link MpoTag}s in the ExifData or null if there + * are none. + */ + protected List<MpoTag> getAllTags() { + ArrayList<MpoTag> ret = new ArrayList<MpoTag>(); + MpoTag[] tags = mMpIndexIfdData.getAllTags(); + if (tags != null) { + for (MpoTag t : tags) { + ret.add(t); + } + } + + tags = mMpAttribIfdData.getAllTags(); + if (tags != null) { + for (MpoTag t : tags) { + ret.add(t); + } + } + + if (ret.size() == 0) { + return null; + } + return ret; + } + + /** + * Returns a list of all {@link MpoTag}s in a given IFD or null if there are + * none. + */ + protected List<MpoTag> getAllTagsForIfd(int ifd) { + MpoTag[] tags = getMpIfdData(ifd).getAllTags(); + if (tags == null) { + return null; + } + ArrayList<MpoTag> ret = new ArrayList<MpoTag>(tags.length); + for (MpoTag t : tags) { + ret.add(t); + } + if (ret.size() == 0) { + return null; + } + return ret; + } + + /** + * Returns a list of all {@link MpoTag}s with a given TID or null if there + * are none. + */ + protected List<MpoTag> getAllTagsForTagId(short tag) { + ArrayList<MpoTag> ret = new ArrayList<MpoTag>(); + MpoTag t = mMpIndexIfdData.getTag(tag); + if (t != null) { + ret.add(t); + } + + t = mMpAttribIfdData.getTag(tag); + if (t != null) { + ret.add(t); + } + + if (ret.size() == 0) { + return null; + } + return ret; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof MpoImageData) { + MpoImageData data = (MpoImageData) obj; + if (data.mByteOrder != mByteOrder) { + return false; + } + + MpoIfdData indexIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD); + MpoIfdData indexIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD); + if (indexIfd1 != indexIfd2 && indexIfd1 != null && !indexIfd1.equals(indexIfd2)) { + return false; + } + + MpoIfdData attribIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD); + MpoIfdData attribIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD); + if (attribIfd1 != attribIfd2 && attribIfd1 != null && !attribIfd1.equals(attribIfd2)) { + return false; + } + return true; + } + return false; + } + + private int calculateOffsetOfIfd(MpoIfdData ifd, int offset) { + offset += 2 + ifd.getTagCount() * MpoTag.TAG_SIZE + 4; + MpoTag[] tags = ifd.getAllTags(); + for (MpoTag tag : tags) { + if (tag.getDataSize() > 4) { + tag.setOffset(offset); + offset += tag.getDataSize(); + } + } + return offset; + } + + public int calculateAllIfdOffsets() { + int offset = MP_HEADER_SIZE; + MpoIfdData indexIfd = getIndexIfdData(); + if (indexIfd.getTagCount() > 0) + offset = calculateOffsetOfIfd(indexIfd, offset); + + MpoIfdData attribIfd = getAttribIfdData(); + if (attribIfd.getTagCount() > 0) { + indexIfd.setOffsetToNextIfd(offset); + offset = calculateOffsetOfIfd(attribIfd, offset); + } + + return offset; + } + + public int calculateImageSize() { + return 2 + APP_HEADER_SIZE + calculateAllIfdOffsets() + mJpegData.length; + } +} diff --git a/src/com/android/camera/mpo/MpoInterface.java b/src/com/android/camera/mpo/MpoInterface.java new file mode 100644 index 000000000..3e2d9a4e2 --- /dev/null +++ b/src/com/android/camera/mpo/MpoInterface.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a contribution. + * + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.mpo; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import android.util.Log; + +import com.android.camera.exif.ExifInterface; +import com.android.camera.util.CameraUtil; + +public class MpoInterface { + private static final String TAG = "MpoInterface"; + private static final String NULL_ARGUMENT_STRING = "Argument is null"; + + // Index IFD + public static final int TAG_MP_FORMAT_VERSION = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_INDEX_IFD + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB000); + public static final int TAG_NUM_IMAGES = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD, + (short) 0xB001); + public static final int TAG_MP_ENTRY = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD, + (short) 0xB002); + public static final int TAG_IMAGE_UNIQUE_ID_LIST = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB003); + public static final int TAG_NUM_CAPTURED_FRAMES = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB004); + + // Attrib IFD + public static final int TAG_IMAGE_NUMBER = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB101); + public static final int TAG_PAN_ORIENTATION = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB201); + public static final int TAG_PAN_OVERLAP_H = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB202); + public static final int TAG_PAN_OVERLAP_V = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB203); + public static final int TAG_BASE_VIEWPOINT_NUM = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB204); + public static final int TAG_CONVERGE_ANGLE = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB205); + public static final int TAG_BASELINE_LEN = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB206); + public static final int TAG_DIVERGE_ANGLE = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB207); + public static final int TAG_AXIS_DISTANCE_X = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB208); + public static final int TAG_AXIS_DISTANCE_Y = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB209); + public static final int TAG_AXIS_DISTANCE_Z = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20A); + public static final int TAG_YAW_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD, + (short) 0xB20B); + public static final int TAG_PITCH_ANGLE = ExifInterface.defineTag( + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20C); + public static final int TAG_ROLL_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD, + (short) 0xB20D); + + public static int writeMpo(MpoData mpo, OutputStream out) { + if (mpo == null || out == null) + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + + MpoOutputStream s = getMpoWriterStream(out); + s.setMpoData(mpo); + + // check and write mpo file + try { + s.writeMpoFile(); + } catch (IOException e) { + CameraUtil.closeSilently(s); + Log.w(TAG, "IO Exception when writing mpo image"); + return -1; + } + + // close stream + CameraUtil.closeSilently(s); + return s.size(); + } + + public static int writeMpo(MpoData mpo, String outFilename) { + if (mpo == null || outFilename == null) + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + + return writeMpo(mpo, getFileWriterStream(outFilename)); + } + + /** + * Wraps an OutputStream object with an MpoOutputStream. + * + * @param outStream + * an OutputStream to wrap. + * @return an MpoOutputStream that wraps the outStream parameter, and adds + * mpo metadata. A jpeg image should be written to this stream. + */ + private static MpoOutputStream getMpoWriterStream(OutputStream outStream) { + if (outStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + MpoOutputStream mos = new MpoOutputStream(outStream); + return mos; + } + + /** + * Returns an FileOutputStream object that writes to a file. + * + * @param outFileName + * an String containing a filepath for a file. + * @return an FileOutputStream that writes to the outFileName file. + * @throws FileNotFoundException + */ + private static OutputStream getFileWriterStream(String outFileName) { + if (outFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream out = null; + try { + out = new FileOutputStream(outFileName); + } catch (FileNotFoundException e) { + CameraUtil.closeSilently(out); + Log.w(TAG, "File not found"); + } + return out; + } +} diff --git a/src/com/android/camera/mpo/MpoOutputStream.java b/src/com/android/camera/mpo/MpoOutputStream.java new file mode 100644 index 000000000..6e8e72fd9 --- /dev/null +++ b/src/com/android/camera/mpo/MpoOutputStream.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a contribution/ + * + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.mpo; + +import java.io.BufferedOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; + +import android.util.Log; + +import com.android.camera.exif.JpegHeader; +import com.android.camera.exif.OrderedDataOutputStream; +import com.android.camera.mpo.MpoTag.MpEntry; + +class MpoOutputStream extends FilterOutputStream { + private static final String TAG = "MpoOutputStream"; + private static final boolean DEBUG = true; + private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb + + private static final int STATE_SOI = 0; + private static final int STATE_FRAME_HEADER = 1; + private static final int STATE_JPEG_DATA = 3; + + private static final short TIFF_HEADER = 0x002A; + private static final short TIFF_BIG_ENDIAN = 0x4d4d; + private static final short TIFF_LITTLE_ENDIAN = 0x4949; + private static final int MAX_EXIF_SIZE = 65535; + + private MpoData mMpoData; + private MpoImageData mCurrentImageData; + private int mState = STATE_SOI; + private int mByteToSkip; + private int mByteToCopy; + private byte[] mSingleByteArray = new byte[1]; + private ByteBuffer mBuffer = ByteBuffer.allocate(4); + private int mMpoOffsetStart = -1; + private int mSize = 0; + + protected MpoOutputStream(OutputStream ou) { + super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE)); + } + + /** + * Sets the ExifData to be written into the JPEG file. Should be called + * before writing image data. + */ + protected void setMpoData(MpoData mpoData) { + mMpoData = mpoData; + mMpoData.updateAllTags(); + } + + private void resetStates() { + mState = STATE_SOI; + mByteToSkip = 0; + mByteToCopy = 0; + mBuffer.rewind(); + } + + private int requestByteToBuffer(int requestByteCount, byte[] buffer, int offset, int length) { + int byteNeeded = requestByteCount - mBuffer.position(); + int byteToRead = length > byteNeeded ? byteNeeded : length; + mBuffer.put(buffer, offset, byteToRead); + return byteToRead; + } + + void writeMpoFile() throws IOException { + // check and write primary image + mCurrentImageData = mMpoData.getPrimaryMpoImage(); + write(mCurrentImageData.getJpegData()); + flush(); + + // check and write auxiliary images + for (MpoImageData image : mMpoData.getAuxiliaryMpoImages()) { + resetStates(); + mCurrentImageData = image; + write(mCurrentImageData.getJpegData()); + flush(); + } + } + + /** + * Writes the image out. The input data should be a valid JPEG format. After + * writing, it's Exif header will be replaced by the given header. + */ + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) && length > 0) { + if (mByteToSkip > 0) { + int byteToProcess = length > mByteToSkip ? mByteToSkip : length; + length -= byteToProcess; + mByteToSkip -= byteToProcess; + offset += byteToProcess; + } + if (mByteToCopy > 0) { + int byteToProcess = length > mByteToCopy ? mByteToCopy : length; + out.write(buffer, offset, byteToProcess); + mSize += byteToProcess; + length -= byteToProcess; + mByteToCopy -= byteToProcess; + offset += byteToProcess; + } + if (length == 0) { + return; + } + switch (mState) { + case STATE_SOI: + int byteRead = requestByteToBuffer(2, buffer, offset, length); + offset += byteRead; + length -= byteRead; + if (mBuffer.position() < 2) { + return; + } + mBuffer.rewind(); + if (mBuffer.getShort() != JpegHeader.SOI) { + throw new IOException("Not a valid jpeg image, cannot write exif"); + } + out.write(mBuffer.array(), 0, 2); + mSize += 2; + mState = STATE_FRAME_HEADER; + mBuffer.rewind(); + break; + case STATE_FRAME_HEADER: + // Copy APP1 if it exists + // Insert MPO data + // Copy remainder of image + byteRead = requestByteToBuffer(4, buffer, offset, length); + offset += byteRead; + length -= byteRead; + // Check if this image data doesn't contain SOF. + if (mBuffer.position() == 2) { + short tag = mBuffer.getShort(); + if (tag == JpegHeader.EOI) { + out.write(mBuffer.array(), 0, 2); + mSize += 2; + mBuffer.rewind(); + } + } + if (mBuffer.position() < 4) { + return; + } + mBuffer.rewind(); + short marker = mBuffer.getShort(); + if (marker == JpegHeader.APP1 || marker == JpegHeader.APP0) { + out.write(mBuffer.array(), 0, 4); + mSize += 4; + mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; + } else { + writeMpoData(); + out.write(mBuffer.array(), 0, 4); + mSize += 4; + mState = STATE_JPEG_DATA; + } + mBuffer.rewind(); + break; + } + } + if (length > 0) { + out.write(buffer, offset, length); + mSize += length; + } + } + + /** + * Writes the one bytes out. The input data should be a valid JPEG format. + * After writing, it's Exif header will be replaced by the given header. + */ + @Override + public void write(int oneByte) throws IOException { + mSingleByteArray[0] = (byte) (0xff & oneByte); + write(mSingleByteArray); + } + + /** + * Equivalent to calling write(buffer, 0, buffer.length). + */ + @Override + public void write(byte[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + private void writeMpoData() throws IOException { + if (mMpoData == null) { + return; + } + if (DEBUG) { + Log.v(TAG, "Writing mpo data..."); + } + int exifSize = mCurrentImageData.calculateAllIfdOffsets() + MpoImageData.APP_HEADER_SIZE; + if (exifSize > MAX_EXIF_SIZE) { + throw new IOException("Exif header is too large (>64Kb)"); + } + OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out); + dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); + dataOutputStream.writeShort(JpegHeader.APP2); + dataOutputStream.writeShort((short) (exifSize)); + dataOutputStream.writeInt(MpoImageData.MP_FORMAT_IDENTIFIER); + if (mMpoOffsetStart == -1) { + mMpoOffsetStart = mSize + dataOutputStream.size(); + } + if (mCurrentImageData.getByteOrder() == ByteOrder.BIG_ENDIAN) { + dataOutputStream.writeShort(TIFF_BIG_ENDIAN); + } else { + dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN); + } + dataOutputStream.setByteOrder(mCurrentImageData.getByteOrder()); + dataOutputStream.writeShort(TIFF_HEADER); + if (exifSize > MpoImageData.MP_HEADER_SIZE + MpoImageData.APP_HEADER_SIZE) { + dataOutputStream.writeInt(MpoImageData.OFFSET_TO_FIRST_IFD); + writeAllTags(dataOutputStream); + } else + dataOutputStream.writeInt(0); + + mSize += dataOutputStream.size(); + } + + private void updateIndexIfdOffsets(MpoIfdData indexIfd, int mpoOffset) { + // update offsets + MpoTag mpEntryTag = mMpoData.getPrimaryMpoImage().getTag((short) MpoInterface.TAG_MP_ENTRY, + MpoIfdData.TYPE_MP_INDEX_IFD); + List<MpEntry> mpEntries = mpEntryTag.getMpEntryValue(); + for (int i = 1; i < mpEntries.size(); i++) { // primary offset is always + // 0 + MpEntry entry = mpEntries.get(i); + entry.setImageOffset(entry.getImageOffset() - mpoOffset); + } + + mpEntryTag.setValue(mpEntries); + } + + private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException { + MpoIfdData indexIfd = mCurrentImageData.getIndexIfdData(); + if (indexIfd.getTagCount() > 0) { + updateIndexIfdOffsets(indexIfd, mMpoOffsetStart); + writeIfd(indexIfd, dataOutputStream); + } + + MpoIfdData attribIfd = mCurrentImageData.getAttribIfdData(); + if (attribIfd.getTagCount() > 0) + writeIfd(attribIfd, dataOutputStream); + } + + private void writeIfd(MpoIfdData ifd, OrderedDataOutputStream dataOutputStream) + throws IOException { + MpoTag[] tags = ifd.getAllTags(); + dataOutputStream.writeShort((short) tags.length); + for (MpoTag tag : tags) { + dataOutputStream.writeShort(tag.getTagId()); + dataOutputStream.writeShort(tag.getDataType()); + dataOutputStream.writeInt(tag.getComponentCount()); + if (DEBUG) { + Log.v(TAG, "\n" + tag.toString()); + } + if (tag.getDataSize() > 4) { + dataOutputStream.writeInt(tag.getOffset()); + } else { + MpoOutputStream.writeTagValue(tag, dataOutputStream); + for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) { + dataOutputStream.write(0); + } + } + } + dataOutputStream.writeInt(ifd.getOffsetToNextIfd()); + for (MpoTag tag : tags) { + if (tag.getDataSize() > 4) { + MpoOutputStream.writeTagValue(tag, dataOutputStream); + } + } + } + + static void writeTagValue(MpoTag tag, OrderedDataOutputStream dataOutputStream) + throws IOException { + switch (tag.getDataType()) { + case MpoTag.TYPE_ASCII: + byte buf[] = tag.getStringByte(); + if (buf.length == tag.getComponentCount()) { + buf[buf.length - 1] = 0; + dataOutputStream.write(buf); + } else { + dataOutputStream.write(buf); + dataOutputStream.write(0); + } + break; + case MpoTag.TYPE_LONG: + case MpoTag.TYPE_UNSIGNED_LONG: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeInt((int) tag.getValueAt(i)); + } + break; + case MpoTag.TYPE_RATIONAL: + case MpoTag.TYPE_UNSIGNED_RATIONAL: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeRational(tag.getRational(i)); + } + break; + case MpoTag.TYPE_UNDEFINED: + case MpoTag.TYPE_UNSIGNED_BYTE: + buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + dataOutputStream.write(buf); + break; + case MpoTag.TYPE_UNSIGNED_SHORT: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeShort((short) tag.getValueAt(i)); + } + break; + } + } + + int size() { + return mSize; + } +} diff --git a/src/com/android/camera/mpo/MpoTag.java b/src/com/android/camera/mpo/MpoTag.java new file mode 100644 index 000000000..bc0e6ce30 --- /dev/null +++ b/src/com/android/camera/mpo/MpoTag.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.camera.mpo; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import android.util.Log; + +import com.android.camera.exif.ExifTag; + +public class MpoTag extends ExifTag { + private static final String TAG = "MpoTag"; + static final int TAG_SIZE = 12; + + MpoTag(short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount) { + super(tagId, type, componentCount, ifd, hasDefinedComponentCount); + } + + public boolean setValue(List<MpEntry> entries) { + if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) { + return false; + } + + byte[] bytes = new byte[entries.size() * MpEntry.SIZE]; + for (int i = 0; i < entries.size(); i++) { + MpEntry entry = entries.get(i); + entry.getBytes(ByteBuffer.wrap(bytes, i * MpEntry.SIZE, MpEntry.SIZE)); + } + return setValue(bytes); + } + + public List<MpEntry> getMpEntryValue() { + if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) { + return null; + } + + byte[] bytes = getValueAsBytes(); + List<MpEntry> entries = new ArrayList<MpEntry>(bytes.length / MpEntry.SIZE); + for (int i = 0; i < bytes.length; i += MpEntry.SIZE) { + entries.add(new MpEntry(ByteBuffer.wrap(bytes, i, MpEntry.SIZE))); + } + return entries; + } + + static class MpEntry { + static final int SIZE = 16; + private int mImageAttrib; + private int mImageSize; + private int mImageOffset; + private short mDependantImage1; + private short mDependantImage2; + + public MpEntry() { + this(0, 0, 0, (short) 0, (short) 0); + } + + public MpEntry(int imageAttrib, int imageSize, int imageOffset) { + this(imageAttrib, imageSize, imageOffset, (short) 0, (short) 0); + } + + public MpEntry(int imageAttrib, int imageSize, int imageOffset, short dependantImage1, + short dependantImage2) { + mImageAttrib = imageAttrib; + mImageSize = imageSize; + mImageOffset = imageOffset; + mDependantImage1 = dependantImage1; + mDependantImage2 = dependantImage2; + } + + public MpEntry(ByteBuffer buffer) { + mImageAttrib = buffer.getInt(); + mImageSize = buffer.getInt(); + mImageOffset = buffer.getInt(); + mDependantImage1 = buffer.getShort(); + mDependantImage2 = buffer.getShort(); + } + + public int getImageAttrib() { + return mImageAttrib; + } + + public int getImageSize() { + return mImageSize; + } + + public int getImageOffset() { + return mImageOffset; + } + + public short getDependantImage1() { + return mDependantImage1; + } + + public short getDependantImage2() { + return mDependantImage2; + } + + public void setImageAttrib(int imageAttrib) { + mImageAttrib = imageAttrib; + } + + public void setImageSize(int imageSize) { + mImageSize = imageSize; + } + + public void setImageOffset(int imageOffset) { + mImageOffset = imageOffset; + } + + public void setDependantImage1(short depImage1) { + mDependantImage1 = depImage1; + } + + public void setDependantImage2(short depImage2) { + mDependantImage2 = depImage2; + } + + public boolean getBytes(ByteBuffer buffer) { + try { + buffer.putInt(mImageAttrib); + buffer.putInt(mImageSize); + buffer.putInt(mImageOffset); + buffer.putShort(mDependantImage1); + buffer.putShort(mDependantImage2); + } catch (BufferOverflowException e) { + Log.w(TAG, "Buffer size too small"); + return false; + } + + return true; + } + } +}
\ No newline at end of file diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java index 587cfcad0..989568953 100644 --- a/src/com/android/camera/ui/CameraControls.java +++ b/src/com/android/camera/ui/CameraControls.java @@ -84,7 +84,7 @@ public class CameraControls extends RotatableLayout { private float[][] mLocY = new float[4][10]; private boolean[] mTempEnabled = new boolean[10]; private boolean mLocSet = false; - + private boolean mHideRemainingPhoto = false; private LinearLayout mRemainingPhotos; private TextView mRemainingPhotosText; private int mCurrentRemaining = -1; @@ -706,7 +706,8 @@ public class CameraControls extends RotatableLayout { mPreview.animate().translationXBy(mSize).setDuration(ANIME_DURATION); break; } - if (mRemainingPhotos.getVisibility() == View.INVISIBLE) { + if ((mRemainingPhotos.getVisibility() == View.INVISIBLE) && + !mHideRemainingPhoto){ mRemainingPhotos.setVisibility(View.VISIBLE); } mRefocusToast.setVisibility(View.GONE); @@ -922,7 +923,7 @@ public class CameraControls extends RotatableLayout { public void updateRemainingPhotos(int remaining) { long remainingStorage = Storage.getAvailableSpace() - Storage.LOW_STORAGE_THRESHOLD_BYTES; - if (remaining < 0 && remainingStorage <= 0) { + if ((remaining < 0 && remainingStorage <= 0) || mHideRemainingPhoto) { mRemainingPhotos.setVisibility(View.GONE); } else { for (int i = mRemainingPhotos.getChildCount() - 1; i >= 0; --i) { @@ -961,7 +962,7 @@ public class CameraControls extends RotatableLayout { public void showRefocusToast(boolean show) { mRefocusToast.setVisibility(show ? View.VISIBLE : View.GONE); - if (mCurrentRemaining > 0 ) { + if ((mCurrentRemaining > 0 ) && !mHideRemainingPhoto) { mRemainingPhotos.setVisibility(show ? View.GONE : View.VISIBLE); } } @@ -1003,6 +1004,12 @@ public class CameraControls extends RotatableLayout { mMenu.setVisibility(View.VISIBLE); } + public void hideRemainingPhotoCnt() { + mHideRemainingPhoto = true; + mRemainingPhotos.setVisibility(View.GONE); + mRemainingPhotosText.setVisibility(View.GONE); + } + private class ArrowTextView extends TextView { private static final int TEXT_SIZE = 14; private static final int PADDING_SIZE = 18; diff --git a/src/com/android/camera/ui/ZoomRenderer.java b/src/com/android/camera/ui/ZoomRenderer.java index 7a2e2e805..0358bda7a 100644 --- a/src/com/android/camera/ui/ZoomRenderer.java +++ b/src/com/android/camera/ui/ZoomRenderer.java @@ -49,11 +49,16 @@ public class ZoomRenderer extends OverlayRenderer private int mZoomFraction; private Rect mTextBounds; private int mOrientation; + private boolean mCamera2 = false; + private float mZoomValue; + private float mZoomMinValue; + private float mZoomMaxValue; public interface OnZoomChangedListener { void onZoomStart(); void onZoomEnd(); void onZoomValueChanged(int index); // only for immediate zoom + void onZoomValueChanged(float value); } public ZoomRenderer(Context ctx) { @@ -81,10 +86,23 @@ public class ZoomRenderer extends OverlayRenderer mMinZoom = 0; } + public void setZoomMax(float zoomMax) { + mCamera2 = true; + mZoomMaxValue = zoomMax; + mZoomMinValue = 1f; + } + public void setZoom(int index) { mCircleSize = mMinCircle + index * (mMaxCircle - mMinCircle) / (mMaxZoom - mMinZoom); } + public void setZoom(float zoomValue) { + mCamera2 = true; + mZoomValue = zoomValue; + mCircleSize = (int) (mMinCircle + (mMaxCircle - mMinCircle) * (mZoomValue - mZoomMinValue) / + (mZoomMaxValue - mZoomMinValue)); + } + public void setZoomValue(int value) { value = value / 10; mZoomSig = value / 10; @@ -120,6 +138,7 @@ public class ZoomRenderer extends OverlayRenderer canvas.drawCircle((float) mCenterX, (float) mCenterY, mCircleSize, mPaint); String txt = mZoomSig+"."+mZoomFraction+"x"; + if (mCamera2) txt = "" + mZoomValue; mTextPaint.getTextBounds(txt, 0, txt.length(), mTextBounds); canvas.drawText(txt, mCenterX - mTextBounds.centerX(), mCenterY - mTextBounds.centerY(), mTextPaint); @@ -133,8 +152,16 @@ public class ZoomRenderer extends OverlayRenderer circle = Math.min(mMaxCircle, circle); if (mListener != null && circle != mCircleSize) { mCircleSize = circle; - int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) / (mMaxCircle - mMinCircle)); - mListener.onZoomValueChanged(zoom); + if (mCamera2) { + float zoom = mZoomMinValue + (mZoomMaxValue - mZoomMinValue) / (mMaxCircle - + mMinCircle) * (mCircleSize - mMinCircle); + zoom = ((int) (zoom * 10)) / 10.0f; + mListener.onZoomValueChanged(zoom); + } else { + int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) / + (mMaxCircle - mMinCircle)); + mListener.onZoomValueChanged(zoom); + } update(); } return true; diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java index 812220c1a..b1deca6e0 100644 --- a/src/com/android/camera/util/CameraUtil.java +++ b/src/com/android/camera/util/CameraUtil.java @@ -35,6 +35,7 @@ import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; +import android.hardware.camera2.CameraCharacteristics; import android.location.Location; import android.net.Uri; import android.os.Handler; @@ -1168,4 +1169,32 @@ public class CameraUtil { return retRatio; } + public static boolean isZoomSupported(CameraCharacteristics[] characteristics, List<Integer> + characteristicsIndex) { + for (int i = 0; i < characteristicsIndex.size(); i++) { + if (!isZoomSupported(characteristics[characteristicsIndex.get(i)])) + return false; + } + return true; + } + + public static boolean isZoomSupported(CameraCharacteristics characteristic) { + return characteristic.get(CameraCharacteristics + .SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) > 1f; + } + + public static boolean isAutoFocusSupported(CameraCharacteristics[] characteristics, List<Integer> + characteristicsIndex) { + for (int i = 0; i < characteristicsIndex.size(); i++) { + if (!isAutoFocusSupported(characteristics[characteristicsIndex.get(i)])) + return false; + } + return true; + } + + public static boolean isAutoFocusSupported(CameraCharacteristics characteristic) { + Integer maxAfRegions = characteristic.get( + CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + return maxAfRegions != null && maxAfRegions > 0; + } } diff --git a/src/com/android/camera/util/ClearSightNativeEngine.java b/src/com/android/camera/util/ClearSightNativeEngine.java new file mode 100644 index 000000000..cf0a740d7 --- /dev/null +++ b/src/com/android/camera/util/ClearSightNativeEngine.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.camera.util; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.YuvImage; +import android.media.Image; +import android.media.Image.Plane; +import android.util.Log; + +public class ClearSightNativeEngine { + private static final String TAG = "ClearSightNativeEngine"; + static { + try { + System.loadLibrary("jni_clearsight"); + mLibLoaded = true; + Log.v(TAG, "successfully loaded clearsight lib"); + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "failed to load clearsight lib"); + e.printStackTrace(); + mLibLoaded = false; + } + } + + private static final int METADATA_SIZE = 5; + private static final int Y_PLANE = 0; + private static final int VU_PLANE = 2; + + // dummy OTP calib data + private static final String otp_calib = "Calibration OTP format version = 10301\n" + + "Main Native Sensor Resolution width = 4224px\n" + + "Main Native Sensor Resolution height = 3136px\n" + + "Main Calibration Resolution width = 1280px\n" + + "Main Calibration Resolution height = 950px\n" + + "Main Focal length ratio = 1.004896\n" + + "Aux Native Sensor Resolution width = 1600px\n" + + "Aux Native Sensor Resolution height = 1200px\n" + + "Aux Calibration Resolution width = 1280px\n" + + "Aux Calibration Resolution height = 960px\n" + + "Aux Focal length ratio = 1.000000\n" + + "Relative Rotation matrix [0] through [8] = 1.000000,-0.003008,0.000251,0.003073,1.000189,0.003329,0.019673,-0.003329,1.000284\n" + + "Relative Geometric surface parameters [0] through [31] = -0.307164,-0.879074,4.636152,0.297486,-0.157539,-6.889396,0.109467,-2.797022,-0.066306,-0.120142,0.196464,0.021974,2.905827,0.241197,0.048328,-5.116615,0.496533,-5.263813,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000\n" + + "Relative Principal point X axis offset (ox) = 0.000000px\n" + + "Relative Principal point Y axis offset (oy) = 0.000000px\n" + + "Relative position flag = 1\n" + + "Baseline distance = 20.000000mm\n" + + "Main sensor mirror and flip setting = 3\n" + + "Aux sensor mirror and flip setting = 3\n" + + "Module orientation during calibration = 0\n" + + "Rotation flag = 0\n" + + "Main Normalized Focal length = 1000.0px\n" + + "Aux Normalized Focal length = 1000.0px"; + + private static boolean mLibLoaded; + private static ClearSightNativeEngine mInstance; + + private Image mRefColorImage; + private Image mRefMonoImage; + private ArrayList<SourceImage> mSrcColor = new ArrayList<SourceImage>(); + private ArrayList<SourceImage> mSrcMono = new ArrayList<SourceImage>(); + + private ClearSightNativeEngine() { + } + + public static void createInstance() { + if (mInstance == null) { + mInstance = new ClearSightNativeEngine(); + } + } + + public static ClearSightNativeEngine getInstance() { + createInstance(); + return mInstance; + } + + public boolean isLibLoaded() { + return mLibLoaded; + } + + public void reset() { + mSrcColor.clear(); + mSrcMono.clear(); + setReferenceColorImage(null); + setReferenceMonoImage(null); + } + + public void setReferenceImage(boolean color, Image image) { + if (color) + setReferenceColorImage(image); + else + setReferenceMonoImage(image); + } + + private void setReferenceColorImage(Image reference) { + if (mRefColorImage != null) { + mRefColorImage.close(); + mRefColorImage = null; + } + + mRefColorImage = reference; + + if (mRefColorImage != null) { + Log.e(TAG, + "setRefColorImage - isdirectbuff: " + + mRefColorImage.getPlanes()[0].getBuffer() + .isDirect()); + mSrcColor.add(new SourceImage(mRefColorImage.getPlanes()[Y_PLANE] + .getBuffer(), mRefColorImage.getPlanes()[VU_PLANE] + .getBuffer(), new int[] { 0, 0, 0, 0, 0 })); + } + } + + private void setReferenceMonoImage(Image reference) { + if (mRefMonoImage != null) { + mRefMonoImage.close(); + mRefMonoImage = null; + } + + mRefMonoImage = reference; + + if (mRefMonoImage != null) { + Log.e(TAG, + "setRefMonoImage - isdirectbuff: " + + mRefMonoImage.getPlanes()[0].getBuffer() + .isDirect()); + mSrcMono.add(new SourceImage(mRefMonoImage.getPlanes()[Y_PLANE] + .getBuffer(), null, new int[] { 0, 0, 0, 0, 0 })); + } + } + + public boolean hasReferenceImage(boolean color) { + return !(color ? mSrcColor.isEmpty() : mSrcMono.isEmpty()); + } + + public Image getReferenceImage(boolean color) { + return color ? mRefColorImage : mRefMonoImage; + } + + public boolean registerImage(boolean color, Image image) { + return (color ? registerColorImage(image) : registerMonoImage(image)); + } + + private boolean registerColorImage(Image image) { + if (mSrcColor.isEmpty()) { + Log.w(TAG, "reference color image not yet set"); + return false; + } + + Plane[] planes = image.getPlanes(); + ByteBuffer refY = mRefColorImage.getPlanes()[Y_PLANE].getBuffer(); + ByteBuffer refVU = mRefColorImage.getPlanes()[VU_PLANE].getBuffer(); + ByteBuffer regY = ByteBuffer.allocateDirect(refY.capacity()); + ByteBuffer regVU = ByteBuffer.allocateDirect(refVU.capacity()); + int[] metadata = new int[METADATA_SIZE]; + + boolean result = clearSightRegisterImage(refY, + planes[Y_PLANE].getBuffer(), planes[VU_PLANE].getBuffer(), + image.getWidth(), image.getHeight(), + planes[Y_PLANE].getRowStride(), + planes[VU_PLANE].getRowStride(), regY, regVU, metadata); + + if (result) { + mSrcColor.add(new SourceImage(regY, regVU, metadata)); + } + + image.close(); + return result; + } + + private boolean registerMonoImage(Image image) { + if (mSrcMono.isEmpty()) { + Log.w(TAG, "reference mono image not yet set"); + return false; + } + + Plane[] planes = image.getPlanes(); + ByteBuffer refY = mRefMonoImage.getPlanes()[Y_PLANE].getBuffer(); + ByteBuffer regY = ByteBuffer.allocateDirect(refY.capacity()); + int[] metadata = new int[METADATA_SIZE]; + + boolean result = clearSightRegisterImage(refY, + planes[Y_PLANE].getBuffer(), null, image.getWidth(), + image.getHeight(), planes[Y_PLANE].getRowStride(), 0, regY, + null, metadata); + + if (result) { + mSrcMono.add(new SourceImage(regY, null, metadata)); + } + + image.close(); + return result; + } + + public ClearsightImage processImage() { + // check data validity + if (mSrcColor.size() != mSrcMono.size()) { + // mis-match in num images + Log.e(TAG, "processImage - numImages mismatch - bayer: " + + mSrcColor.size() + ", mono: " + mSrcMono.size()); + return null; + } + + int numImages = mSrcColor.size(); + ByteBuffer[] srcColorY = new ByteBuffer[numImages]; + ByteBuffer[] srcColorVU = new ByteBuffer[numImages]; + int[][] metadataColor = new int[numImages][]; + ByteBuffer[] srcMonoY = new ByteBuffer[numImages]; + int[][] metadataMono = new int[numImages][]; + + Log.e(TAG, "processImage - numImages: " + numImages); + + for (int i = 0; i < numImages; i++) { + SourceImage color = mSrcColor.get(i); + SourceImage mono = mSrcMono.get(i); + + srcColorY[i] = color.mY; + srcColorVU[i] = color.mVU; + metadataColor[i] = color.mMetadata; + + srcMonoY[i] = mono.mY; + metadataMono[i] = mono.mMetadata; + } + + Plane[] colorPlanes = mRefColorImage.getPlanes(); + Plane[] monoPlanes = mRefMonoImage.getPlanes(); + ByteBuffer dstY = ByteBuffer.allocateDirect(colorPlanes[Y_PLANE] + .getBuffer().capacity()); + ByteBuffer dstVU = ByteBuffer.allocateDirect(colorPlanes[VU_PLANE] + .getBuffer().capacity()); + int[] roiRect = new int[4]; + + boolean result = clearSightProcess(numImages, srcColorY, srcColorVU, + metadataColor, mRefColorImage.getWidth(), + mRefColorImage.getHeight(), + colorPlanes[Y_PLANE].getRowStride(), + colorPlanes[VU_PLANE].getRowStride(), srcMonoY, metadataMono, + mRefMonoImage.getWidth(), mRefMonoImage.getHeight(), + monoPlanes[Y_PLANE].getRowStride(), otp_calib.getBytes(), dstY, dstVU, + colorPlanes[Y_PLANE].getRowStride(), + colorPlanes[VU_PLANE].getRowStride(), roiRect); + + if (result) { + dstY.rewind(); + dstVU.rewind(); + byte[] data = new byte[dstY.capacity() + dstVU.capacity()]; + int[] strides = new int[] { colorPlanes[Y_PLANE].getRowStride(), + colorPlanes[VU_PLANE].getRowStride() }; + dstY.get(data, 0, dstY.capacity()); + dstVU.get(data, dstY.capacity(), dstVU.capacity()); + return new ClearsightImage(new YuvImage(data, ImageFormat.NV21, + mRefColorImage.getWidth(), mRefColorImage.getHeight(), + strides), roiRect); + } else { + return null; + } + } + + native public boolean configureClearSight(float focalLengthRatio, + float brIntensity, float sharpenIntensity); + + native public boolean clearSightRegisterImage(ByteBuffer refY, + ByteBuffer srcY, ByteBuffer srcVU, int width, int height, + int strideY, int strideVU, ByteBuffer dstY, ByteBuffer dstVU, + int[] metadata); + + native public boolean clearSightProcess(int numImagePairs, + ByteBuffer[] srcColorY, ByteBuffer[] srcColorVU, + int[][] metadataColor, int srcColorWidth, int srcColorHeight, + int srcColorStrideY, int srcColorStrideVU, ByteBuffer[] srcMonoY, + int[][] metadataMono, int srcMonoWidth, int srcMonoHeight, + int srcMonoStrideY, byte[] otp, ByteBuffer dstY, ByteBuffer dstVU, + int dstStrideY, int dstStrideVU, int[] roiRect); + + private class SourceImage { + ByteBuffer mY; + ByteBuffer mVU; + int[] mMetadata; + + SourceImage(ByteBuffer y, ByteBuffer vu, int[] metadata) { + mY = y; + mVU = vu; + mMetadata = metadata; + } + } + + public static class ClearsightImage { + private YuvImage mImage; + private Rect mRoiRect; + + ClearsightImage(YuvImage image, int[] rect) { + mImage = image; + mRoiRect = new Rect(rect[0], rect[1], rect[0] + rect[2], rect[1] + + rect[3]); + } + + public Rect getRoiRect() { + return mRoiRect; + } + + public long getDataLength() { + return (mImage==null?0:mImage.getYuvData().length); + } + + public int getWidth() { + return (mRoiRect.right - mRoiRect.left); + } + + public int getHeight() { + return (mRoiRect.bottom - mRoiRect.top); + } + + public byte[] compressToJpeg() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + mImage.compressToJpeg(mRoiRect, 100, baos); + return baos.toByteArray(); + } + } +} |