diff --git a/camera/.clang-format b/camera/.clang-format index 77b4a86..a6eb858 100644 --- a/camera/.clang-format +++ b/camera/.clang-format @@ -11,7 +11,7 @@ AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All +AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None @@ -50,7 +50,7 @@ CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 +ContinuationIndentWidth: 8 Cpp11BracedListStyle: true DerivePointerAlignment: true DisableFormat: false diff --git a/camera/MultiCameraApplication/AndroidManifest.xml b/camera/MultiCameraApplication/AndroidManifest.xml index 2919c63..3be0329 100644 --- a/camera/MultiCameraApplication/AndroidManifest.xml +++ b/camera/MultiCameraApplication/AndroidManifest.xml @@ -1,52 +1,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java index b3d28b9..12ce3b5 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java @@ -26,8 +26,12 @@ public class AutoFitTextureView extends TextureView { private int mRatioWidth = 0; private int mRatioHeight = 0; - public AutoFitTextureView(Context context) { this(context, null); } - public AutoFitTextureView(Context context, AttributeSet attrs) { this(context, attrs, 0); } + public AutoFitTextureView(Context context) { + this(context, null); + } + public AutoFitTextureView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java index 6a05ae0..1d7459d 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class BotmLeftCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class BotmLeftCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,24 +160,31 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) {} + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 3) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } cameraId = manager.getCameraIdList()[2]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); @@ -176,23 +192,29 @@ public void openCamera() { characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); + + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); } - mMediaRecorder = new MediaRecorder(); + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -213,21 +235,46 @@ public void onOpened(CameraDevice camera) { } @Override public void onDisconnected(CameraDevice camera) { - cameraDevice.close(); + Log.e(TAG, "onDisconnected"); + closeCamera(); } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; - + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( @@ -246,31 +293,44 @@ protected void createCameraPreview() { Surface surface = new Surface(texture); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; + Arrays.asList(surface), new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (null == cameraDevice) { + return; + } + // When the session is ready, we start displaying the preview. + cameraCaptureSessions = cameraCaptureSession; + updatePreview(); } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) + .show(); + } + }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -281,11 +341,7 @@ public void closeCamera() { imageReader.close(); imageReader = null; } - if (null != mMediaRecorder) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } + releaseMedia(); stopBackgroundThread(); } @@ -293,7 +349,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera-3"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -329,6 +385,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -336,32 +406,24 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); - + settings.getString("capture_list", "640x480")); + Log.d(TAG, "Selected imageDimension" + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( - imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); + imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.addTarget(reader.getSurface()); captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -370,77 +432,78 @@ protected void takePicture() { } mPictureFilename = fileDetails[3]; mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); + Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, + imageDimension.getWidth(), imageDimension.getHeight()); file = new File(mPictureFilename); ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); + new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + Image image = null; + try { + image = reader.acquireLatestImage(); + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + jpegLength = bytes; + mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, + jpegLength); + + save(bytes); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (image != null) { + image.close(); + } } } - } - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); + private void save(byte[] bytes) throws IOException { + OutputStream output = null; + try { + output = new FileOutputStream(file); + output.write(bytes); + } finally { + if (null != output) { + output.close(); + } } } - } - }; + }; reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + super.onCaptureCompleted(session, request, result); + Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); + + createCameraPreview(); + } + }; cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); + outputSurfaces, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + try { + session.capture(captureRequestBuilder.build(), captureListener, + mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } } - } - @Override - public void onConfigureFailed(CameraCaptureSession session) {} - }, mBackgroundHandler); + @Override + public void onConfigureFailed(CameraCaptureSession session) { + } + }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -448,11 +511,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -507,9 +577,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -518,19 +589,34 @@ private void setUpMediaRecorder() throws IOException { } mVideoFilename = fileDetails[3]; mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, - mProfile.videoFrameHeight); + Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, + mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); - + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); - mMediaRecorder.reset(); + releaseMedia(); throw new RuntimeException(ex); } } @@ -546,9 +632,9 @@ private void closePreviewSession() { private void stopRecordingVideo() { mIsRecordingVideo = false; TakeVideoButton.setText(R.string.record); + // Stop recording - mMediaRecorder.stop(); - mMediaRecorder.reset(); + releaseMedia(); if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java index 6fec868..3772ccd 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class BotmRightCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class BotmRightCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,48 +160,59 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) {} + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (manager.getCameraIdList().length != 4) { + Log.e(TAG, "this camera is not connected "); + return; + } + cameraId = manager.getCameraIdList()[3]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; - } - mMediaRecorder = new MediaRecorder(); + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); + } + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -218,16 +238,41 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( @@ -247,31 +292,44 @@ protected void createCameraPreview() { Surface surface = new Surface(texture); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; + Arrays.asList(surface), new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (null == cameraDevice) { + return; + } + // When the session is ready, we start displaying the preview. + cameraCaptureSessions = cameraCaptureSession; + updatePreview(); } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) + .show(); + } + }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -282,11 +340,7 @@ public void closeCamera() { imageReader.close(); imageReader = null; } - if (null != mMediaRecorder) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } + releaseMedia(); stopBackgroundThread(); } @@ -294,7 +348,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera-4"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -330,6 +384,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -337,32 +405,24 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); - + settings.getString("capture_list", "640x480")); + Log.d(TAG, "Selected imageDimension" + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( - imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); + imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.addTarget(reader.getSurface()); captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -371,77 +431,78 @@ protected void takePicture() { } mPictureFilename = fileDetails[3]; mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); + Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, + imageDimension.getWidth(), imageDimension.getHeight()); file = new File(mPictureFilename); ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); + new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + Image image = null; + try { + image = reader.acquireLatestImage(); + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + jpegLength = bytes; + mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, + jpegLength); + + save(bytes); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (image != null) { + image.close(); + } } } - } - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); + private void save(byte[] bytes) throws IOException { + OutputStream output = null; + try { + output = new FileOutputStream(file); + output.write(bytes); + } finally { + if (null != output) { + output.close(); + } } } - } - }; + }; reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + super.onCaptureCompleted(session, request, result); + Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); + + createCameraPreview(); + } + }; cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); + outputSurfaces, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + try { + session.capture(captureRequestBuilder.build(), captureListener, + mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } } - } - @Override - public void onConfigureFailed(CameraCaptureSession session) {} - }, mBackgroundHandler); + @Override + public void onConfigureFailed(CameraCaptureSession session) { + } + }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -449,11 +510,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -508,9 +576,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -519,19 +588,34 @@ private void setUpMediaRecorder() throws IOException { } mVideoFilename = fileDetails[3]; mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, - mProfile.videoFrameHeight); + Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, + mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); - + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); - mMediaRecorder.reset(); + releaseMedia(); throw new RuntimeException(ex); } } @@ -547,9 +631,9 @@ private void closePreviewSession() { private void stopRecordingVideo() { mIsRecordingVideo = false; TakeVideoButton.setText(R.string.record); + // Stop recording - mMediaRecorder.stop(); - mMediaRecorder.reset(); + releaseMedia(); if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java index 30a040b..1b85b06 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +17,7 @@ package com.intel.multicamera; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -34,6 +36,7 @@ import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @@ -41,17 +44,19 @@ public class MainActivity extends AppCompatActivity { * An {@link AutoFitTextureView} for camera preview. */ private AutoFitTextureView mTopLeftCam_textureView, mTopRightCam_textureView, - mBotmLeftCam_textureView, mBotmRightCam_textureView; + mBotmLeftCam_textureView, mBotmRightCam_textureView; private Button mTopLeftCam_PictureButton, mTopRightCam_PictureButton, - mBotmLeftCam_PictureButton, mBotmRightCam_PictureButton, mTopLeftCam_RecordButton, - mTopRightCam_RecordButton, mBotmLeftCam_RecordButton, mBotmRightCam_RecordButton; + mBotmLeftCam_PictureButton, mBotmRightCam_PictureButton, mTopLeftCam_RecordButton, + mTopRightCam_RecordButton, mBotmLeftCam_RecordButton, mBotmRightCam_RecordButton; private int numOfCameras; private static final int REQUEST_CAMERA_PERMISSION = 200; private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private FrameLayout frameView0, frameView1, frameView2, frameView3; + private boolean mHasCriticalPermissions = false; + TopLeftCam mTopLeftCam; TopRightCam mTopRightCam; BotmLeftCam mBotmLeftCam; @@ -71,12 +76,19 @@ protected void onCreate(Bundle savedInstanceState) { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + checkPermissions(); + if (!mHasCriticalPermissions) { + Log.v(TAG, "onCreate: Missing critical permissions."); + finish(); + return; + } + setVisibilityFrameLayout(); } public void Open_TopLeftCam() { mTopLeftCam_textureView = findViewById(R.id.textureview0); - assert mTopLeftCam_textureView != null; + if (mTopLeftCam_textureView == null) return; mTopLeftCam_PictureButton = findViewById(R.id.Picture0); mTopLeftCam_RecordButton = findViewById(R.id.Record0); @@ -92,7 +104,7 @@ else if (isImageCaptureIntent()) public void Open_TopRightCam() { mTopRightCam_textureView = findViewById(R.id.textureview1); - assert mTopRightCam_textureView != null; + if (mTopRightCam_textureView == null) return; mTopRightCam_PictureButton = findViewById(R.id.Picture1); mTopRightCam_RecordButton = findViewById(R.id.Record1); @@ -103,7 +115,7 @@ public void Open_TopRightCam() { public void Open_BotmLeftCam() { mBotmLeftCam_textureView = findViewById(R.id.textureview2); - assert mTopRightCam_textureView != null; + if (mBotmLeftCam_textureView == null) return; mBotmLeftCam_PictureButton = findViewById(R.id.Picture2); mBotmLeftCam_RecordButton = findViewById(R.id.Record2); @@ -114,7 +126,7 @@ public void Open_BotmLeftCam() { public void Open_BotmRightCam() { mBotmRightCam_textureView = findViewById(R.id.textureview3); - assert mTopRightCam_textureView != null; + if (mBotmRightCam_textureView == null) return; mBotmRightCam_PictureButton = findViewById(R.id.Picture3); mBotmRightCam_RecordButton = findViewById(R.id.Record3); @@ -144,7 +156,7 @@ public void setVisibilityFrameLayout() { numOfCameras = manager.getCameraIdList().length; // if (numCameras != "") { numOfCameras = Integer.parseInt(numCameras); } Log.d(TAG, "onCreate Inside openCamera() Total Cameras: " + - manager.getCameraIdList().length); + manager.getCameraIdList().length); if (numOfCameras == 1) { frameView1.setVisibility(FrameLayout.INVISIBLE); @@ -184,11 +196,10 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults[0] == PackageManager.PERMISSION_DENIED) { // close the app - Toast - .makeText(MainActivity.this, - "Sorry!!!, you can't use this app without granting permission", - Toast.LENGTH_LONG) - .show(); + Toast.makeText(MainActivity.this, + "Sorry!!!, you can't use this app without granting permission", + Toast.LENGTH_LONG) + .show(); finish(); } } @@ -200,26 +211,121 @@ private void manageTopLeftCam() { frameView0.setVisibility(FrameLayout.VISIBLE); } else if (mTopLeftCam_textureView == null) { mTopLeftCam_textureView = findViewById(R.id.textureview0); - assert mTopLeftCam_textureView != null; + if (mTopLeftCam_textureView == null) return; } if (mTopLeftCam_textureView.isAvailable()) { frameView0.setVisibility(FrameLayout.VISIBLE); - mTopLeftCam.openCamera(); + mTopLeftCam.openCamera(mTopLeftCam_textureView.getWidth(), + mTopLeftCam_textureView.getHeight()); } else { mTopLeftCam_textureView.setSurfaceTextureListener(mTopLeftCam.textureListener); } } + private void manageTopRightCam() { + if (mTopRightCam == null) { + Open_TopRightCam(); + frameView1.setVisibility(FrameLayout.VISIBLE); + + } else if (mTopRightCam_textureView == null) { + mTopRightCam_textureView = findViewById(R.id.textureview1); + if (mTopRightCam_textureView == null) return; + } + + if (mTopRightCam_textureView.isAvailable()) { + frameView1.setVisibility(FrameLayout.VISIBLE); + mTopRightCam.openCamera(mTopRightCam_textureView.getWidth(), + mTopRightCam_textureView.getHeight()); + } else { + mTopRightCam_textureView.setSurfaceTextureListener(mTopRightCam.textureListener); + } + } + + private void manageBotmLeftCam() { + if (mBotmLeftCam == null) { + Open_BotmLeftCam(); + frameView2.setVisibility(FrameLayout.VISIBLE); + + } else if (mBotmLeftCam_textureView == null) { + mBotmLeftCam_textureView = findViewById(R.id.textureview2); + if (mBotmLeftCam_textureView == null) return; + } + + if (mBotmLeftCam_textureView.isAvailable()) { + frameView2.setVisibility(FrameLayout.VISIBLE); + mBotmLeftCam.openCamera(mBotmLeftCam_textureView.getWidth(), + mBotmLeftCam_textureView.getHeight()); + } else { + mBotmLeftCam_textureView.setSurfaceTextureListener(mBotmLeftCam.textureListener); + } + } + + private void manageBotmRightCam() { + if (mBotmRightCam == null) { + Open_BotmRightCam(); + frameView3.setVisibility(FrameLayout.VISIBLE); + + } else if (mBotmRightCam_textureView == null) { + mBotmRightCam_textureView = findViewById(R.id.textureview3); + if (mBotmRightCam_textureView == null) return; + } + + if (mBotmRightCam_textureView.isAvailable()) { + frameView3.setVisibility(FrameLayout.VISIBLE); + mBotmRightCam.openCamera(mBotmRightCam_textureView.getWidth(), + mBotmRightCam_textureView.getHeight()); + } else { + mBotmRightCam_textureView.setSurfaceTextureListener(mBotmRightCam.textureListener); + } + } + + /** + * Checks if any of the needed Android runtime permissions are missing. + * If they are, then launch the permissions activity under one of the following conditions: + * a) The permissions dialogs have not run yet. We will ask for permission only once. + * b) If the missing permissions are critical to the app running, we will display a fatal error + * dialog. Critical permissions are: camera, microphone and storage. The app cannot run without + * them. Non-critical permission is location. + */ + private void checkPermissions() { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.CAMERA) == + PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.READ_EXTERNAL_STORAGE) == + PackageManager.PERMISSION_GRANTED) { + mHasCriticalPermissions = true; + } else { + mHasCriticalPermissions = false; + } + + if (!mHasCriticalPermissions) { + Intent intent = new Intent(this, PermissionsActivity.class); + startActivity(intent); + finish(); + } + } + @Override protected void onResume() { super.onResume(); Log.e(TAG, "onResume"); + checkPermissions(); + if (!mHasCriticalPermissions) { + Log.v(TAG, "onResume: Missing critical permissions."); + finish(); + return; + } + CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); try { numOfCameras = manager.getCameraIdList().length; - Log.d(TAG, "Total Cameras: " + manager.getCameraIdList().length); + Log.d(TAG, "onResume Total Cameras: " + manager.getCameraIdList().length); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -227,17 +333,17 @@ protected void onResume() { if (numOfCameras == 1) { manageTopLeftCam(); } else if (numOfCameras == 2) { - if (mTopLeftCam_textureView.isAvailable()) { - mTopLeftCam.openCamera(); - } else { - mTopLeftCam_textureView.setSurfaceTextureListener(mTopLeftCam.textureListener); - } - - if (mTopRightCam_textureView.isAvailable()) { - mTopRightCam.openCamera(); - } else { - mTopRightCam_textureView.setSurfaceTextureListener(mTopRightCam.textureListener); - } + manageTopLeftCam(); + manageTopRightCam(); + } else if (numOfCameras == 3) { + manageTopLeftCam(); + manageBotmLeftCam(); + manageTopRightCam(); + } else if (numOfCameras == 4) { + manageTopLeftCam(); + manageTopRightCam(); + manageBotmLeftCam(); + manageBotmRightCam(); } else { Log.d(TAG, "onResume No CAMERA CONNECTED"); frameView0.setVisibility(FrameLayout.INVISIBLE); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java new file mode 100644 index 0000000..d790051 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014 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.intel.multicamera; + +import android.Manifest; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.*; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Window; +import android.view.WindowManager; +import androidx.core.app.ActivityCompat; +import androidx.preference.PreferenceManager; + +/** + * Activity that shows permissions request dialogs and handles lack of critical permissions. + */ +public class PermissionsActivity extends QuickActivity { + private String TAG = "PermissionsActivity"; + + private static int PERMISSION_REQUEST_CODE = 1; + private static int RESULT_CODE_OK = 1; + private static int RESULT_CODE_FAILED = 2; + + private int mIndexPermissionRequestCamera; + private int mIndexPermissionRequestMicrophone; + private int mIndexPermissionRequestLocation; + private int mIndexPermissionRequestStorage; + private boolean mShouldRequestCameraPermission; + private boolean mShouldRequestMicrophonePermission; + private boolean mShouldRequestLocationPermission; + private boolean mShouldRequestStoragePermission; + private int mNumPermissionsToRequest; + private boolean mFlagHasCameraPermission; + private boolean mFlagHasMicrophonePermission; + private boolean mFlagHasStoragePermission; + + /** + * Close activity when secure app passes lock screen or screen turns + * off. + + private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "received intent, finishing: " + intent.getAction()); + finish(); + } + }; + */ + + @Override + protected void onCreateTasks(Bundle savedInstanceState) { + setContentView(R.layout.permissions); + + // Filter for screen off so that we can finish permissions activity + // when screen is off. + // IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF); + // registerReceiver(mShutdownReceiver, filter_screen_off); + + Window win = getWindow(); + win.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + + @Override + protected void onResumeTasks() { + mNumPermissionsToRequest = 0; + checkPermissions(); + } + + @Override + protected void onDestroyTasks() { + Log.v(TAG, "onDestroy: unregistering receivers"); + + // unregisterReceiver(mShutdownReceiver); + } + + /** + * Package private conversion method to turn String storage format into + * booleans. + * + * @param value String to be converted to boolean + * @return boolean value of stored String + */ + public boolean convertToBoolean(String value) { + boolean ret = false; + + if (value.compareTo("false") == 0) { + ret = false; + Log.d(TAG, "####FALSE"); + + } else if (value.compareTo("true") == 0) { + Log.d(TAG, "####TRUE"); + ret = true; + } + + return ret; + } + + /** + * Retrieve a setting's value as a String, manually specifiying + * a default value. + */ + public String getString(String key, String defaultValue) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + try { + return preferences.getString(key, defaultValue); + } catch (ClassCastException e) { + Log.w(TAG, "existing preference with invalid type, removing and returning default", e); + preferences.edit().remove(key).apply(); + return defaultValue; + } + } + + private void checkPermissions() { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.CAMERA) != + PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestCameraPermission = true; + } else { + mFlagHasCameraPermission = true; + } + + if (ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.RECORD_AUDIO) != + PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestMicrophonePermission = true; + } else { + mFlagHasMicrophonePermission = true; + } + + if (ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestStoragePermission = true; + } else { + mFlagHasStoragePermission = true; + } + + if (mNumPermissionsToRequest != 0) { + if (!convertToBoolean(getString("pref_has_seen_permissions_dialogs", "false"))) { + buildPermissionsRequest(); + } else { + // Permissions dialog has already been shown, or we're on + // lockscreen, and we're still missing permissions. + handlePermissionsFailure(); + } + } else { + handlePermissionsSuccess(); + } + } + + private void buildPermissionsRequest() { + String[] permissionsToRequest = new String[mNumPermissionsToRequest]; + int permissionsRequestIndex = 0; + + if (mShouldRequestCameraPermission) { + permissionsToRequest[permissionsRequestIndex] = Manifest.permission.CAMERA; + mIndexPermissionRequestCamera = permissionsRequestIndex; + permissionsRequestIndex++; + } + if (mShouldRequestMicrophonePermission) { + permissionsToRequest[permissionsRequestIndex] = Manifest.permission.RECORD_AUDIO; + mIndexPermissionRequestMicrophone = permissionsRequestIndex; + permissionsRequestIndex++; + } + if (mShouldRequestStoragePermission) { + permissionsToRequest[permissionsRequestIndex] = + Manifest.permission.WRITE_EXTERNAL_STORAGE; + mIndexPermissionRequestStorage = permissionsRequestIndex; + permissionsRequestIndex++; + } + + Log.v(TAG, "requestPermissions count: " + permissionsToRequest.length); + requestPermissions(permissionsToRequest, PERMISSION_REQUEST_CODE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], + int[] grantResults) { + Log.v(TAG, "onPermissionsResult counts: " + permissions.length + ":" + grantResults.length); + + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + settings.edit().putString("pref_has_seen_permissions_dialogs", "true").apply(); + + if (mShouldRequestCameraPermission) { + if (grantResults.length > 0 && + grantResults[mIndexPermissionRequestCamera] == PackageManager.PERMISSION_GRANTED) { + mFlagHasCameraPermission = true; + } else { + handlePermissionsFailure(); + } + } + if (mShouldRequestMicrophonePermission) { + if (grantResults.length > 0 && grantResults[mIndexPermissionRequestMicrophone] == + PackageManager.PERMISSION_GRANTED) { + mFlagHasMicrophonePermission = true; + } else { + handlePermissionsFailure(); + } + } + if (mShouldRequestStoragePermission) { + if (grantResults.length > 0 && + grantResults[mIndexPermissionRequestStorage] == PackageManager.PERMISSION_GRANTED) { + mFlagHasStoragePermission = true; + } else { + handlePermissionsFailure(); + } + } + + if (mFlagHasCameraPermission && mFlagHasMicrophonePermission && mFlagHasStoragePermission) { + handlePermissionsSuccess(); + } else { + // Permissions dialog has already been shown + // and we're still missing permissions. + handlePermissionsFailure(); + } + } + + private void handlePermissionsSuccess() { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } + + private void handlePermissionsFailure() { + new AlertDialog.Builder(this) + .setTitle(getResources().getString(R.string.camera_error_title)) + .setMessage(getResources().getString(R.string.error_permissions)) + .setCancelable(false) + .setOnKeyListener(new Dialog.OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + finish(); + } + return true; + } + }) + .setNegativeButton(getResources().getString(R.string.dialog_dismiss), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .show(); + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java new file mode 100644 index 0000000..b839edb --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2014 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.intel.multicamera; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; + +/** + * Workaround for lockscreen double-onResume() bug: + *

+ * We track 3 startup situations: + *

    + *
  • Normal startup -- e.g. from GEL.
  • + *
  • Secure lock screen startup -- e.g. with a keycode.
  • + *
  • Non-secure lock screen startup -- e.g. with just a swipe.
  • + *
+ * The KeyguardManager service can be queried to determine which state we are in. + * If started from the lock screen, the activity may be quickly started, + * resumed, paused, stopped, and then started and resumed again. This is + * problematic for launch time from the lock screen because we typically open the + * camera in onResume() and close it in onPause(). These camera operations take + * a long time to complete. To workaround it, this class filters out + * high-frequency onResume()->onPause() sequences if the KeyguardManager + * indicates that we have started from the lock screen. + *

+ *

+ * Subclasses should override the appropriate on[Create|Start...]Tasks() method + * in place of the original. + *

+ *

+ * Sequences of onResume() followed quickly by onPause(), when the activity is + * started from a lockscreen will result in a quick no-op.
+ *

+ */ +public abstract class QuickActivity extends Activity { + private String TAG = "QuickActivity"; + + /** onResume tasks delay from secure lockscreen. */ + private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30; + /** onResume tasks delay from non-secure lockscreen. */ + private static final long ON_RESUME_DELAY_NON_SECURE_MILLIS = 15; + + /** A reference to the main handler on which to run lifecycle methods. */ + private Handler mMainHandler; + + /** + * True if onResume tasks have been skipped, and made false again once they + * are executed within the onResume() method or from a delayed Runnable. + */ + private boolean mSkippedFirstOnResume = false; + + /** When application execution started in SystemClock.elapsedRealtimeNanos(). */ + protected long mExecutionStartNanoTime = 0; + /** Was this session started with onCreate(). */ + protected boolean mStartupOnCreate = false; + + /** + * A runnable for deferring tasks to be performed in onResume() if starting + * from the lockscreen. + */ + private final Runnable mOnResumeTasks = new Runnable() { + @Override + public void run() { + if (mSkippedFirstOnResume) { + Log.v(TAG, "delayed Runnable --> onResumeTasks()"); + // Doing the tasks, can set to false. + mSkippedFirstOnResume = false; + onResumeTasks(); + } + } + }; + + @Override + protected final void onNewIntent(Intent intent) { + logLifecycle("onNewIntent", true); + Log.v(TAG, "Intent Action = " + intent.getAction()); + setIntent(intent); + super.onNewIntent(intent); + onNewIntentTasks(intent); + logLifecycle("onNewIntent", false); + } + + @Override + protected final void onCreate(Bundle bundle) { + mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos(); + logLifecycle("onCreate", true); + mStartupOnCreate = true; + super.onCreate(bundle); + mMainHandler = new Handler(getMainLooper()); + onCreateTasks(bundle); + logLifecycle("onCreate", false); + } + + @Override + protected final void onStart() { + logLifecycle("onStart", true); + onStartTasks(); + super.onStart(); + logLifecycle("onStart", false); + } + + @Override + protected final void onResume() { + logLifecycle("onResume", true); + + // For lockscreen launch, there are two possible flows: + // 1. onPause() does not occur before mOnResumeTasks is executed: + // Runnable mOnResumeTasks sets mSkippedFirstOnResume to false + // 2. onPause() occurs within ON_RESUME_DELAY_*_MILLIS: + // a. Runnable mOnResumeTasks is removed + // b. onPauseTasks() is skipped, mSkippedFirstOnResume remains true + // c. next onResume() will immediately execute onResumeTasks() + // and set mSkippedFirstOnResume to false + + mMainHandler.removeCallbacks(mOnResumeTasks); + if (mSkippedFirstOnResume == false) { + long delay = mSkippedFirstOnResume ? ON_RESUME_DELAY_SECURE_MILLIS + : ON_RESUME_DELAY_NON_SECURE_MILLIS; + // Skipping onResumeTasks; set to true. + mSkippedFirstOnResume = true; + Log.v(TAG, "onResume() --> postDelayed(mOnResumeTasks," + delay + ")"); + mMainHandler.postDelayed(mOnResumeTasks, delay); + } else { + Log.v(TAG, "onResume --> onResumeTasks()"); + // Doing the tasks, can set to false. + mSkippedFirstOnResume = false; + onResumeTasks(); + } + super.onResume(); + logLifecycle("onResume", false); + } + + @Override + protected final void onPause() { + logLifecycle("onPause", true); + mMainHandler.removeCallbacks(mOnResumeTasks); + // Only run onPauseTasks if we have not skipped onResumeTasks in a + // first call to onResume. If we did skip onResumeTasks (note: we + // just killed any delayed Runnable), we also skip onPauseTasks to + // adhere to lifecycle state machine. + if (mSkippedFirstOnResume == false) { + Log.v(TAG, "onPause --> onPauseTasks()"); + onPauseTasks(); + } + super.onPause(); + mStartupOnCreate = false; + logLifecycle("onPause", false); + } + + @Override + protected final void onStop() { + if (isChangingConfigurations()) { + Log.v(TAG, "changing configurations"); + } + logLifecycle("onStop", true); + onStopTasks(); + super.onStop(); + logLifecycle("onStop", false); + } + + @Override + protected final void onRestart() { + logLifecycle("onRestart", true); + super.onRestart(); + // TODO Support onRestartTasks() and handle the workaround for that too. + logLifecycle("onRestart", false); + } + + @Override + protected final void onDestroy() { + logLifecycle("onDestroy", true); + onDestroyTasks(); + super.onDestroy(); + logLifecycle("onDestroy", false); + } + + private void logLifecycle(String methodName, boolean start) { + String prefix = start ? "START" : "END"; + Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString()); + } + + /** + * Subclasses should override this in place of {@link Activity#onNewIntent}. + */ + protected void onNewIntentTasks(Intent newIntent) { + } + + /** + * Subclasses should override this in place of {@link Activity#onCreate}. + */ + protected void onCreateTasks(Bundle savedInstanceState) { + } + + /** + * Subclasses should override this in place of {@link Activity#onStart}. + */ + protected void onStartTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onResume}. + */ + protected void onResumeTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onPause}. + */ + protected void onPauseTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onStop}. + */ + protected void onStopTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onDestroy}. + */ + protected void onDestroyTasks() { + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java index 68f7231..bf385e1 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java @@ -64,9 +64,9 @@ protected void onCreate(Bundle savedInstanceState) { if (GlobalVariable.numOfCameras != 0) getSupportedSize(manager); getFragmentManager() - .beginTransaction() - .replace(R.id.settings, new SettingsFragment()) - .commit(); + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); } public void getSupportedSize(CameraManager manager) { @@ -80,12 +80,12 @@ public void getSupportedSize(CameraManager manager) { CameraCharacteristics characteristics = manager.getCameraCharacteristics(camerId); StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { } GlobalVariable.SupportedSizes = - new ArrayList<>(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG))); + new ArrayList<>(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG))); } catch (CameraAccessException e) { e.printStackTrace(); @@ -93,7 +93,7 @@ public void getSupportedSize(CameraManager manager) { } public static class SettingsFragment - extends PreferenceFragment implements OnSharedPreferenceChangeListener { + extends PreferenceFragment implements OnSharedPreferenceChangeListener { public String TAG = "SettingsFragment"; public static final String SIZE_LARGE = "large"; public static final String SIZE_MEDIUM = "medium"; @@ -105,9 +105,9 @@ public static class SettingsFragment private static final String SIZE_SETTING_STRING_DIMENSION_DELIMITER = "x"; public static SparseArray sCachedSelectedVideoQualities = - new SparseArray(3); + new SparseArray(3); private static String mPrefChangedKey = null; - static boolean isPrefChangedKeyChnaged = false; + static boolean isPrefChangedKeyChanged = false; /** The selected {@link CamcorderProfile} qualities. */ public static class SelectedVideoQualities { public int large = -1; @@ -133,11 +133,11 @@ public int getFromSetting(String sizeSetting) { /** Video qualities sorted by size. */ public static int[] sVideoQualities = - new int[] {// CamcorderProfile.QUALITY_HIGH, - CamcorderProfile.QUALITY_1080P, CamcorderProfile.QUALITY_720P, - CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_CIF, - CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_QCIF, - CamcorderProfile.QUALITY_LOW}; + new int[] {// CamcorderProfile.QUALITY_HIGH, + CamcorderProfile.QUALITY_1080P, CamcorderProfile.QUALITY_720P, + CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_CIF, + CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_QCIF, + CamcorderProfile.QUALITY_LOW}; static SelectedVideoQualities VideoQualities; public int numOfCameras = GlobalVariable.numOfCameras; @@ -150,7 +150,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names); - isPrefChangedKeyChnaged = false; + isPrefChangedKeyChanged = false; } @Override @@ -171,7 +171,7 @@ public void onResume() { // Put in the summaries for the currently set values. final PreferenceGroup Prf_Resolution = - (PreferenceGroup)findPreference("pref_resolution"); + (PreferenceGroup)findPreference("pref_resolution"); fillEntriesAndSummaries(Prf_Resolution); } @@ -179,26 +179,25 @@ public void onResume() { Log.d(TAG, "SettingsFragment onResume end"); getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener( - this); + this); } @Override public void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener( - this); + this); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String Key) { setSummary(findPreference(Key)); mPrefChangedKey = Key; - isPrefChangedKeyChnaged = true; + isPrefChangedKeyChanged = true; } public static String getchangedPrefKey() { - if (isPrefChangedKeyChnaged == true) { - isPrefChangedKeyChnaged = false; + if (isPrefChangedKeyChanged == true) { return mPrefChangedKey; } else { return DEFAULT_KEY; @@ -302,8 +301,8 @@ public static Size sizeFromSettingString(String sizeSettingString) { private String getSizeSummaryString(Size size) { int width = size.getWidth(); int height = size.getHeight(); - String result = - getResources().getString(R.string.setting_summary_width_and_height, width, height); + String result = getResources().getString(R.string.setting_summary_width_and_height, + width, height); return result; } diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java index 3233405..83c95d9 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -30,9 +33,11 @@ import android.media.ImageReader; import android.media.MediaRecorder; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.util.Log; import android.util.Size; @@ -71,16 +76,19 @@ public class TopLeftCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; + private Uri mCurrentVideoUri = null; + private ParcelFileDescriptor mVideoFileDescriptor = null; /** * Whether the app is recording video now */ @@ -93,6 +101,10 @@ public class TopLeftCam { byte[] jpegLength; private boolean mIsVideoCaptureIntent, mIsImageCaptureIntent, mIsonDoneClicked; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; static { ORIENTATIONS.append(Surface.ROTATION_0, 90); @@ -193,56 +205,88 @@ public void onClick(View view) { }); } - public void onDoneClicked() { doReturnToCaller(true); } + public void onDoneClicked() { + doReturnToCaller(true); + } + + private void doReturnToCaller(boolean valid) { + Intent resultIntent = new Intent(); + int resultCode; + if (mIsVideoCaptureIntent) { + mIsVideoCaptureIntent = false; + resultCode = Activity.RESULT_OK; + resultIntent.setData(mCurrentVideoUri); + resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + mActivity.setResult(resultCode, resultIntent); + } else if (mIsImageCaptureIntent) { + mIsImageCaptureIntent = false; + resultCode = Activity.RESULT_OK; + mActivity.setResult(resultCode); + } - private void doReturnToCaller(boolean valid) { mActivity.finish(); } + mActivity.finish(); + } TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) {} + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 1) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } + cameraId = manager.getCameraIdList()[0]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); } - mMediaRecorder = new MediaRecorder(); + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -269,18 +313,42 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; int quality = -1; - + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); String videoQuality = settings.getString("video_list", "medium"); @@ -300,31 +368,44 @@ protected void createCameraPreview() { Surface surface = new Surface(texture); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; + Arrays.asList(surface), new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (null == cameraDevice) { + return; + } + // When the session is ready, we start displaying the preview. + cameraCaptureSessions = cameraCaptureSession; + updatePreview(); } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) + .show(); + } + }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -335,11 +416,8 @@ public void closeCamera() { imageReader.close(); imageReader = null; } - if (null != mMediaRecorder) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } + releaseMedia(); + closeVideoFileDescriptor(); stopBackgroundThread(); } @@ -347,7 +425,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera_0"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -383,6 +461,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -390,8 +482,11 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + settings.getString("capture_list", "640x480")); + Log.d(TAG, "Selected imageDimension " + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); @@ -399,23 +494,13 @@ protected void takePicture() { outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.addTarget(reader.getSurface()); captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -424,77 +509,78 @@ protected void takePicture() { } mPictureFilename = fileDetails[3]; mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); + Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, + imageDimension.getWidth(), imageDimension.getHeight()); file = new File(mPictureFilename); ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); + new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + Image image = null; + try { + image = reader.acquireLatestImage(); + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + jpegLength = bytes; + mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, + jpegLength); + + save(bytes); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (image != null) { + image.close(); + } } } - } - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); + private void save(byte[] bytes) throws IOException { + OutputStream output = null; + try { + output = new FileOutputStream(file); + output.write(bytes); + } finally { + if (null != output) { + output.close(); + } } } - } - }; + }; reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + super.onCaptureCompleted(session, request, result); + Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); + + createCameraPreview(); + } + }; cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); + outputSurfaces, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + try { + session.capture(captureRequestBuilder.build(), captureListener, + mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } } - } - @Override - public void onConfigureFailed(CameraCaptureSession session) {} - }, mBackgroundHandler); + @Override + public void onConfigureFailed(CameraCaptureSession session) { + } + }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -502,11 +588,19 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -562,30 +656,73 @@ private void setUpMediaRecorder() throws IOException { return; } + String result = null; + ContentResolver mContentResolver = mActivity.getContentResolver(); + + Intent intent = mActivity.getIntent(); + Bundle extras = intent.getExtras(); + + closeVideoFileDescriptor(); + + if (mIsVideoCaptureIntent && extras != null) { + Uri saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT); + if (saveUri != null) { + try { + mVideoFileDescriptor = mContentResolver.openFileDescriptor(saveUri, "rw"); + mCurrentVideoUri = saveUri; + mVideoFilename = Utils.getFileNameFromUri(saveUri); + } catch (java.io.FileNotFoundException ex) { + // invalid uri + Log.e(TAG, ex.toString()); + } + } + } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; + if (mVideoFileDescriptor != null) { + mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); + mVideoFilename = "CtsCameraIntents.mp4"; + } else { + String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); + if (fileDetails == null || fileDetails.length < 5) { + Log.e(TAG, "Invalid file details"); + return; + } + mVideoFilename = fileDetails[3]; + mCurrentVideoValues = + Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, + mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + /** + * set output file in media recorder + */ + mMediaRecorder.setOutputFile(mVideoFilename); } - mVideoFilename = fileDetails[3]; - mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, - mProfile.videoFrameHeight); - - /** - * set output file in media recorder - */ - mMediaRecorder.setOutputFile(mVideoFilename); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); - mMediaRecorder.reset(); + releaseMedia(); throw new RuntimeException(ex); } } @@ -601,16 +738,27 @@ private void closePreviewSession() { private void stopRecordingVideo() { mIsRecordingVideo = false; TakeVideoButton.setText(R.string.record); + // Stop recording - mMediaRecorder.stop(); - mMediaRecorder.reset(); + releaseMedia(); if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); Log.d(TAG, "Video saved: " + mVideoFilename); } mVideoFilename = null; - + closeVideoFileDescriptor(); createCameraPreview(); } + + private void closeVideoFileDescriptor() { + if (mVideoFileDescriptor != null) { + try { + mVideoFileDescriptor.close(); + } catch (IOException e) { + Log.e(TAG, "Fail to close fd", e); + } + mVideoFileDescriptor = null; + } + } } diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java index e0f824a..21e73c2 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class TopRightCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class TopRightCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,49 +160,60 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) {} + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 2) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } cameraId = manager.getCameraIdList()[1]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; - } + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - mMediaRecorder = new MediaRecorder(); + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); + } + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -219,17 +239,42 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( @@ -249,31 +294,44 @@ protected void createCameraPreview() { Surface surface = new Surface(texture); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; + Arrays.asList(surface), new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (null == cameraDevice) { + return; + } + // When the session is ready, we start displaying the preview. + cameraCaptureSessions = cameraCaptureSession; + updatePreview(); } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) + .show(); + } + }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -284,11 +342,7 @@ public void closeCamera() { imageReader.close(); imageReader = null; } - if (null != mMediaRecorder) { - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } + releaseMedia(); stopBackgroundThread(); } @@ -296,7 +350,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera_1"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -332,6 +386,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -339,32 +407,24 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); - + settings.getString("capture_list", "640x480")); + Log.d(TAG, "Selected imageDimension: " + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( - imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); + imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.addTarget(reader.getSurface()); captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -373,77 +433,78 @@ protected void takePicture() { } mPictureFilename = fileDetails[3]; mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); + Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, + imageDimension.getWidth(), imageDimension.getHeight()); file = new File(mPictureFilename); ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); + new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + Image image = null; + try { + image = reader.acquireLatestImage(); + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + jpegLength = bytes; + mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, + jpegLength); + + save(bytes); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (image != null) { + image.close(); + } } } - } - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); + private void save(byte[] bytes) throws IOException { + OutputStream output = null; + try { + output = new FileOutputStream(file); + output.write(bytes); + } finally { + if (null != output) { + output.close(); + } } } - } - }; + }; reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + super.onCaptureCompleted(session, request, result); + Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); + + createCameraPreview(); + } + }; cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); + outputSurfaces, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + try { + session.capture(captureRequestBuilder.build(), captureListener, + mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } } - } - @Override - public void onConfigureFailed(CameraCaptureSession session) {} - }, mBackgroundHandler); + @Override + public void onConfigureFailed(CameraCaptureSession session) { + } + }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -451,11 +512,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -510,9 +578,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -521,19 +590,35 @@ private void setUpMediaRecorder() throws IOException { } mVideoFilename = fileDetails[3]; mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, - mProfile.videoFrameHeight); + Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, + mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); - mMediaRecorder.reset(); + releaseMedia(); throw new RuntimeException(ex); } } @@ -549,9 +634,9 @@ private void closePreviewSession() { private void stopRecordingVideo() { mIsRecordingVideo = false; TakeVideoButton.setText(R.string.record); + // Stop recording - mMediaRecorder.stop(); - mMediaRecorder.reset(); + releaseMedia(); if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java index ae9e6f1..32a3289 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java @@ -67,8 +67,8 @@ public static File createOutputmediaStorageDir() { } File mediaStorageDir = - new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), - "MultiCamera"); + new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), + "MultiCamera"); // This location works best if you want the created images to be shared // between applications and persist after your app has been uninstalled. @@ -121,6 +121,16 @@ public static void broadcastNewVideo(Context context, ContentValues values) { context.sendBroadcast(new Intent(ACTION_NEW_VIDEO, uri)); } + public static String getFileNameFromUri(Uri uri) { + String result = null; + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + return result; + } + public static String[] generateFileDetails(int type) { File mediaStorageDir = createOutputmediaStorageDir(); if (mediaStorageDir == null) { @@ -184,8 +194,10 @@ public static ContentValues getContentValues(int type, String fileDetails[], int Long.valueOf(fileDetails[4]) / 1000); contentValue.put(MediaStore.Video.Media.MIME_TYPE, fileDetails[2]); contentValue.put(MediaStore.Video.Media.DATA, fileDetails[3]); - contentValue.put(MediaStore.MediaColumns.WIDTH, width); - contentValue.put(MediaStore.MediaColumns.HEIGHT, height); + contentValue.put(MediaStore.Video.Media.WIDTH, width); + contentValue.put(MediaStore.Video.Media.HEIGHT, height); + contentValue.put(MediaStore.Video.Media.RESOLUTION, + Integer.toString(width) + "x" + Integer.toString(height)); } return contentValue; } diff --git a/camera/MultiCameraApplication/res/layout/permissions.xml b/camera/MultiCameraApplication/res/layout/permissions.xml new file mode 100644 index 0000000..eec475b --- /dev/null +++ b/camera/MultiCameraApplication/res/layout/permissions.xml @@ -0,0 +1,21 @@ + + + diff --git a/camera/MultiCameraApplication/res/values/strings.xml b/camera/MultiCameraApplication/res/values/strings.xml index b5b36cd..b97c7ef 100644 --- a/camera/MultiCameraApplication/res/values/strings.xml +++ b/camera/MultiCameraApplication/res/values/strings.xml @@ -59,4 +59,14 @@ "'IMG'_yyyyMMdd_HHmmss" + + + Camera error + + + The app does not have critical permissions needed to run. Please check your permissions settings. + + + Dismiss +