diff options
author | Xin Li <delphij@google.com> | 2019-09-04 13:35:00 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2019-09-04 13:35:00 -0700 |
commit | 874516b3a8c4e14444bfaa17b9ebc294d7899b40 (patch) | |
tree | 6a561f5379c73c4eb7ba64915284437755028320 | |
parent | 8272e7ad1e221440c4438cafdfbad03313354565 (diff) | |
parent | 8200882f623b2a1023a221ad36b0f2db6282ee30 (diff) | |
download | pdk-874516b3a8c4e14444bfaa17b9ebc294d7899b40.tar.gz |
DO NOT MERGE - Merge Android 10 into master
Bug: 139893257
Change-Id: I67a7040a90c369abf5a0c3e7cf0e16f351addb99
8 files changed, 225 insertions, 55 deletions
diff --git a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java index d743443..f796dee 100644 --- a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java +++ b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.SystemClock; +import android.view.OrientationEventListener; import android.view.View; import android.view.Surface; import android.view.SurfaceHolder; @@ -179,6 +180,7 @@ public class TestingCamera extends Activity private static final int PERMISSIONS_REQUEST_CAMERA = 1; private static final int PERMISSIONS_REQUEST_RECORDING = 2; static final int PERMISSIONS_REQUEST_SNAPSHOT = 3; + private OrientationEventHandler mOrientationHandler; /** Activity lifecycle */ @@ -331,6 +333,34 @@ public class TestingCamera extends Activity } mRS = RenderScript.create(this); + + mOrientationHandler = new OrientationEventHandler(this); + } + + private static class OrientationEventHandler extends OrientationEventListener { + private TestingCamera mActivity; + private int mCurrentRotation = -1; + OrientationEventHandler(TestingCamera activity) { + super(activity); + mActivity = activity; + } + + @Override + public void onOrientationChanged(int orientation) { + if (mActivity != null) { + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + if (mCurrentRotation != rotation) { + mCurrentRotation = rotation; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mActivity.setCameraDisplayOrientation(); + mActivity.resizePreview(); + } + }); + } + } + } } @Override @@ -338,6 +368,7 @@ public class TestingCamera extends Activity super.onResume(); log("onResume: Setting up"); setUpCamera(); + mOrientationHandler.enable(); } @Override @@ -361,6 +392,7 @@ public class TestingCamera extends Activity } mState = CAMERA_UNINITIALIZED; } + mOrientationHandler.disable(); } @Override @@ -418,7 +450,7 @@ public class TestingCamera extends Activity } } - if (mPreviewHolder != null) { + if (mPreviewHolder != null || mState == CAMERA_UNINITIALIZED) { return; } log("Surface holder available: " + width + " x " + height); @@ -1037,7 +1069,9 @@ public class TestingCamera extends Activity updateColorEffects(mParams); // Trigger updating video record size to match camcorder profile - mCamcorderProfileSpinner.setSelection(mCamcorderProfile); + if (mCamcorderProfile >= 0) { + mCamcorderProfileSpinner.setSelection(mCamcorderProfile); + } if (mParams.isVideoStabilizationSupported()) { log("Video stabilization is supported"); @@ -1317,11 +1351,18 @@ public class TestingCamera extends Activity mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); } } + String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); mCamcorderProfileSpinner.setAdapter( new ArrayAdapter<String>( this, R.layout.spinner_item, nameArray)); + if (availableCamcorderProfileNames.size() == 0) { + log("Camera " + cameraId + " doesn't support camcorder profile"); + mCamcorderProfile = -1; + return; + } + mCamcorderProfile = 0; log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); @@ -1388,8 +1429,33 @@ public class TestingCamera extends Activity } void layoutPreview() { + int rotation = getWindowManager().getDefaultDisplay().getRotation(); int width = mPreviewSizes.get(mPreviewSize).width; int height = mPreviewSizes.get(mPreviewSize).height; + switch (rotation) { + case Surface.ROTATION_0: + case Surface.ROTATION_180: + // Portrait + // Switch the preview size so that the longer edge aligns with the taller + // dimension. + if (width > height) { + int tmp = height; + height = width; + width = tmp; + } + break; + case Surface.ROTATION_90: + case Surface.ROTATION_270: + // Landscape + // Possibly somewhat unlikely case but we should try to handle it too. + if (height > width) { + int tmp = height; + height = width; + width = tmp; + } + break; + } + float previewAspect = ((float) width) / height; int viewHeight = mPreviewView.getHeight(); @@ -1402,6 +1468,8 @@ public class TestingCamera extends Activity } mPreviewView.setLayoutParams( new LayoutParams(viewWidth, viewHeight)); + log("Setting layout params viewWidth: " + viewWidth + " viewHeight: " + viewHeight + + " display rotation: " + rotation); if (mCallbacksEnabled) { int callbackHeight = mCallbackView.getHeight(); @@ -1569,6 +1637,19 @@ public class TestingCamera extends Activity } } + private static final int BIT_RATE_1080P = 16000000; + private static final int BIT_RATE_MIN = 64000; + private static final int BIT_RATE_MAX = 40000000; + + private int getVideoBitRate(Camera.Size sz) { + int rate = BIT_RATE_1080P; + float scaleFactor = sz.height * sz.width / (float)(1920 * 1080); + rate = (int)(rate * scaleFactor); + + // Clamp to the MIN, MAX range. + return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate)); + } + private void startRecording() { log("Starting recording"); @@ -1608,8 +1689,17 @@ public class TestingCamera extends Activity mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); + Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize); + if (mCamcorderProfile >= 0) { + mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); + } else { + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mRecorder.setVideoEncodingBitRate(getVideoBitRate(videoRecordSize)); + mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + } + if (videoRecordSize.width > 0 && videoRecordSize.height > 0) { mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height); } diff --git a/apps/TestingCamera2/AndroidManifest.xml b/apps/TestingCamera2/AndroidManifest.xml index 79c1b7f..430d8ba 100644 --- a/apps/TestingCamera2/AndroidManifest.xml +++ b/apps/TestingCamera2/AndroidManifest.xml @@ -37,8 +37,6 @@ android:required="false" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:icon="@mipmap/launcher_testingcamera2" diff --git a/apps/TestingCamera2/res/layout/main.xml b/apps/TestingCamera2/res/layout/main.xml index 488265d..acc8594 100644 --- a/apps/TestingCamera2/res/layout/main.xml +++ b/apps/TestingCamera2/res/layout/main.xml @@ -66,7 +66,13 @@ android:spinnerMode="dropdown" android:prompt="@string/camera_id_spinner_prompt" /> - + <Spinner + android:id="@+id/still_format_spinner" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:spinnerMode="dropdown" + android:prompt="@string/still_format_spinner_prompt" + /> <Button android:id="@+id/info_button" android:text="@string/info_button_label" diff --git a/apps/TestingCamera2/res/values/strings.xml b/apps/TestingCamera2/res/values/strings.xml index c3bc711..71c88dc 100644 --- a/apps/TestingCamera2/res/values/strings.xml +++ b/apps/TestingCamera2/res/values/strings.xml @@ -87,7 +87,8 @@ <string name="v1_app_name">TestingCam2</string> <string name="camera_id_spinner_prompt">Camera Id</string> - <string name="info_button_label">JPEG!</string> + <string name="still_format_spinner_prompt">Still Capture Format</string> + <string name="info_button_label">Capture!</string> <string name="flush_button_label">Flush device</string> <string name="focus_lock_button_label">Focus Lock</string> <string name="focus_unlock_button_label">Focus Unlock</string> diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java b/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java index ddf0098..01ba6b6 100644 --- a/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java +++ b/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java @@ -109,18 +109,14 @@ public class CameraOps2 extends CameraManager.AvailabilityCallback { return false; } } - if ((mActivity.checkSelfPermission(Manifest.permission.CAMERA) - != PackageManager.PERMISSION_GRANTED) - || (mActivity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED)) { - TLog.i("Requesting camera/storage permissions"); + if (mActivity.checkSelfPermission(Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + TLog.i("Requesting camera permissions"); mDelayedOpenId = cameraId; mDelayedOpenListener = listener; - mActivity.requestPermissions(new String[] { - Manifest.permission.CAMERA, - Manifest.permission.WRITE_EXTERNAL_STORAGE }, + mActivity.requestPermissions(new String[] {Manifest.permission.CAMERA}, PERMISSIONS_REQUEST_CAMERA); return false; } diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java b/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java index 18534f7..9e82012 100644 --- a/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java +++ b/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java @@ -23,6 +23,7 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.Surface; import android.view.View; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; @@ -159,6 +160,8 @@ public class RequestControlPane extends ControlPane { mTemplateSpinner = (Spinner) findViewById(R.id.request_pane_template_spinner); mOutputListView = (ListView) findViewById(R.id.request_pane_output_listview); + mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); + mOutputAdapter = new CheckableListAdapter(context, R.layout.checkable_list_item, new ArrayList<CheckableListAdapter.CheckableItem>()); mOutputListView.setAdapter(mOutputAdapter); @@ -193,6 +196,18 @@ public class RequestControlPane extends ControlPane { } } + private AdapterView.OnItemSelectedListener mCameraSpinnerListener = new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + updateOutputList(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + updateOutputList(); + } + }; + private OnClickListener mCaptureButtonListener = new OnClickListener() { @Override public void onClick(View v) { diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java b/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java index 753fa00..486c245 100644 --- a/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java +++ b/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java @@ -31,6 +31,7 @@ import android.util.Size; import android.media.Image; import android.media.ImageReader; import android.media.MediaCodec; +import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; @@ -84,8 +85,8 @@ public class CameraOps { List<Surface> mOutputSurfaces = new ArrayList<Surface>(2); private Surface mPreviewSurface; private Surface mPreviewSurface2; - // How many JPEG buffers do we want to hold on to at once - private static final int MAX_CONCURRENT_JPEGS = 2; + // How many still capture buffers do we want to hold on to at once + private static final int MAX_CONCURRENT_STILL_CAPTURES = 2; private static final int STATUS_ERROR = 0; private static final int STATUS_UNINITIALIZED = 1; @@ -408,33 +409,65 @@ public class CameraOps { } } - public void minimalJpegCapture(final CaptureCallback listener, CaptureResultListener l, - Handler h, CameraControls cameraControl) throws ApiFailureException { + private static class SimpleImageListener implements ImageReader.OnImageAvailableListener { + private final ConditionVariable imageAvailable = new ConditionVariable(); + private final CaptureCallback mListener; + + SimpleImageListener(final CaptureCallback listener) { + mListener = listener; + } + + @Override + public void onImageAvailable(ImageReader reader) { + Image i = null; + try { + i = reader.acquireNextImage(); + mListener.onCaptureAvailable(i); + } finally { + if (i != null) { + i.close(); + } + imageAvailable.open(); + } + } + + public void waitForImageAvailable(long timeout) { + if (imageAvailable.block(timeout)) { + imageAvailable.close(); + } else { + Log.e(TAG, "wait for image available timed out after " + timeout + "ms"); + } + } + } + + public void minimalStillCapture(final CaptureCallback listener, CaptureResultListener l, + Handler h, CameraControls cameraControl, int format) throws ApiFailureException { minimalOpenCamera(); try { CameraCharacteristics properties = mCameraManager.getCameraCharacteristics(mCamera.getId()); - Size[] jpegSizes = null; + Size[] stillSizes = null; if (properties != null) { - jpegSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP). - getOutputSizes(ImageFormat.JPEG); + stillSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP). + getOutputSizes(format); } int width = 640; int height = 480; - if (jpegSizes != null && jpegSizes.length > 0) { - width = jpegSizes[0].getWidth(); - height = jpegSizes[0].getHeight(); + if (stillSizes != null && stillSizes.length > 0) { + width = stillSizes[0].getWidth(); + height = stillSizes[0].getHeight(); } if (mCaptureReader == null || mCaptureReader.getWidth() != width || - mCaptureReader.getHeight() != height) { + mCaptureReader.getHeight() != height || + mCaptureReader.getImageFormat() != format) { if (mCaptureReader != null) { mCaptureReader.close(); } mCaptureReader = ImageReader.newInstance(width, height, - ImageFormat.JPEG, MAX_CONCURRENT_JPEGS); + format, MAX_CONCURRENT_STILL_CAPTURES); } List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1); @@ -450,26 +483,15 @@ public class CameraOps { updateCaptureRequest(captureBuilder, cameraControl); - ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image i = null; - try { - i = reader.acquireNextImage(); - listener.onCaptureAvailable(i); - } finally { - if (i != null) { - i.close(); - } - } - } - }; + SimpleImageListener readerListener = new SimpleImageListener(listener); + mCaptureReader.setOnImageAvailableListener(readerListener, h); mSession.capture(captureBuilder.build(), l, mOpsHandler); + + readerListener.waitForImageAvailable(1000L/*timeout*/); } catch (CameraAccessException e) { - throw new ApiFailureException("Error in minimal JPEG capture", e); + throw new ApiFailureException("Error in minimal still capture", e); } } diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java b/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java index 7a4a9b5..27273a8 100644 --- a/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java +++ b/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java @@ -29,6 +29,7 @@ import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.MediaMuxer; @@ -73,6 +74,7 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { private CameraOps mCameraOps; private String[] mAvailableCameraIds; private String mCameraId; + private int mCaptureFormat = ImageFormat.JPEG; private static final int mSeekBarMax = 100; private static final long MAX_EXPOSURE = 200000000L; // 200ms private static final long MIN_EXPOSURE = 100000L; // 100us @@ -94,7 +96,9 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { private SurfaceHolder mCurrentPreviewHolder2 = null; private Spinner mCameraIdSpinner; + private Spinner mCaptureFormatSpinner; private OnItemSelectedListener mCameraIdSelectionListener; + private OnItemSelectedListener mCaptureFormatSelectionListener; private Button mInfoButton; private Button mFlushButton; private ToggleButton mFocusLockToggle; @@ -139,6 +143,7 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { mStillView = (ImageView) findViewById(R.id.still_view); mCameraIdSpinner = (Spinner) findViewById(R.id.camera_id_spinner); + mCaptureFormatSpinner = (Spinner) findViewById(R.id.still_format_spinner); mInfoButton = (Button) findViewById(R.id.info_button); mInfoButton.setOnClickListener(mInfoButtonListener); mFlushButton = (Button) findViewById(R.id.flush_button); @@ -232,14 +237,11 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { if ((checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ) || (checkSelfPermission(Manifest.permission.RECORD_AUDIO) - != PackageManager.PERMISSION_GRANTED) - || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { Log.i(TAG, "Requested camera/video permissions"); requestPermissions(new String[] { Manifest.permission.CAMERA, - Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, + Manifest.permission.RECORD_AUDIO }, PERMISSIONS_REQUEST_CAMERA); return; } @@ -402,14 +404,14 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { @Override public void run() { try { - mCameraOps.minimalJpegCapture(mCaptureCallback, mCaptureResultListener, - uiHandler, mCameraControl); + mCameraOps.minimalStillCapture(mCaptureCallback, mCaptureResultListener, + uiHandler, mCameraControl, mCaptureFormat); if (mCurrentPreviewHolder != null && mCurrentPreviewHolder2 != null) { mCameraOps.minimalPreview(mCurrentPreviewHolder, mCurrentPreviewHolder2, mCameraControl); } } catch (ApiFailureException e) { - logException("Can't take a JPEG! ", e); + logException("Can't take a still capture! ", e); } } }); @@ -448,15 +450,16 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { private final CameraOps.CaptureCallback mCaptureCallback = new CameraOps.CaptureCallback() { @Override public void onCaptureAvailable(Image capture) { - if (capture.getFormat() != ImageFormat.JPEG) { + if (capture.getFormat() != ImageFormat.JPEG && + capture.getFormat() != ImageFormat.HEIC) { Log.e(TAG, "Unexpected format: " + capture.getFormat()); return; } - ByteBuffer jpegBuffer = capture.getPlanes()[0].getBuffer(); - byte[] jpegData = new byte[jpegBuffer.capacity()]; - jpegBuffer.get(jpegData); + ByteBuffer encodedBuffer = capture.getPlanes()[0].getBuffer(); + byte[] encodedData = new byte[encodedBuffer.capacity()]; + encodedBuffer.get(encodedData); - Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); + Bitmap b = BitmapFactory.decodeByteArray(encodedData, 0, encodedData.length); mStillView.setImageBitmap(b); } }; @@ -822,6 +825,45 @@ public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { // Do nothing } }); + + // Map available still capture formats -> capture format spinner dropdown list of + // strings + StreamConfigurationMap streamConfigMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + final List<String> captureFormatList = new ArrayList<>(); + + captureFormatList.add("JPEG"); + if (streamConfigMap.isOutputSupportedFor(ImageFormat.HEIC)) { + captureFormatList.add("HEIC"); + } + + dataAdapter = new ArrayAdapter<>(TestingCamera2.this, + android.R.layout.simple_spinner_item, captureFormatList); + dataAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mCaptureFormatSpinner.setAdapter(dataAdapter); + + /* + * Change the capture format and update preview when format spinner's selected item changes + */ + mCaptureFormatSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + int format = (position == 0 ? ImageFormat.JPEG : ImageFormat.HEIC); + + Log.i(TAG, "Change image capture format to " + captureFormatList.get(position) + + " " + format); + + mCaptureFormat = format; + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Do nothing + } + }); } }; } |