Java tutorial
/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.mediarecorder; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.annotation.Size; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import android.util.SparseIntArray; import android.view.Menu; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.example.android.common.media.CameraHelper; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This activity uses the camera/camcorder as the A/V source for the {@link android.media.MediaRecorder} API. * A {@link android.view.TextureView} is used as the camera preview which limits the code to API 14+. This * can be easily replaced with a {@link android.view.SurfaceView} to run on older devices. */ public class MainActivity extends Activity implements SurfaceHolder.Callback, ActivityCompat.OnRequestPermissionsResultCallback { private static final int REQUEST_ALL_PERMISSIONS = 1; private Camera mCamera; private SurfaceView mPreview; private MediaRecorder mMediaRecorder; private File mOutputFile; private boolean isRecording = false; private static final String TAG = "Recorder"; private Button captureButton; private SurfaceHolder mHolder; private int sensorOrientation; 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(); static { DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180); } static { INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270); INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180); INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90); INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); } private int orientation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sample_main); mPreview = (SurfaceView) findViewById(R.id.surface_view); captureButton = (Button) findViewById(R.id.button_capture); if (checkForPermissions()) { preparePreview(); } } /** * The capture button controls all user interaction. When recording, the button click * stops recording, releases {@link android.media.MediaRecorder} and {@link android.hardware.Camera}. When not recording, * it prepares the {@link android.media.MediaRecorder} and starts recording. * * @param view the view generating the event. */ public void onCaptureClick(View view) { Log.i(TAG, "ON CAPTURE CLICK "); if (isRecording) { // BEGIN_INCLUDE(stop_release_media_recorder) // stop recording and release camera Toast.makeText(this, "Stopping Capture", Toast.LENGTH_SHORT).show(); try { Log.i(TAG, "Stopping Capture"); mMediaRecorder.stop(); // stop the recording Toast.makeText(this, "Saving file: " + mOutputFile, Toast.LENGTH_SHORT).show(); } catch (RuntimeException e) { // RuntimeException is thrown when stop() is called immediately after start(). // In this case the output file is not properly constructed and should be deleted. Log.d(TAG, "RuntimeException: stop() is called immediately after start()"); Toast.makeText(this, "Failed to save video", Toast.LENGTH_SHORT).show(); //noinspection ResultOfMethodCallIgnored mOutputFile.delete(); } releaseMediaRecorder(); // release the MediaRecorder object mCamera.lock(); // take camera access back from MediaRecorder // inform the user that recording has stopped setCaptureButtonText("Capture"); isRecording = false; releaseCamera(); preparePreview(); // END_INCLUDE(stop_release_media_recorder) Log.i(TAG, "Starting new Camera Preview"); preparePreview(); } else { // BEGIN_INCLUDE(prepare_start_media_recorder) new MediaPrepareTask().execute(null, null, null); // END_INCLUDE(prepare_start_media_recorder) } } private void setCaptureButtonText(String title) { captureButton.setText(title); } @Override protected void onPause() { super.onPause(); // if we are using MediaRecorder, release it first releaseMediaRecorder(); // release the camera immediately on pause event releaseCamera(); } private void releaseMediaRecorder() { if (mMediaRecorder != null) { // clear recorder configuration mMediaRecorder.reset(); // release the recorder object mMediaRecorder.release(); mMediaRecorder = null; // Lock camera for later use i.e taking it back from MediaRecorder. // MediaRecorder doesn't need it anymore and we will release it if the activity pauses. mCamera.lock(); } } private void releaseCamera() { if (mCamera != null) { // release the camera for other applications mCamera.release(); mCamera = null; } } private boolean preparePreview() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { try { CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); String id = manager.getCameraIdList()[0]; CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); } catch (CameraAccessException e) { Log.e("ERROR", "Cannot access Camera2 API"); e.printStackTrace(); } } Log.i(TAG, "Preparing Camera Preview"); // BEGIN_INCLUDE (configure_preview) // mCamera = CameraHelper.getDefaultCameraInstance(); // We need to make sure that our preview and recording video size are supported by the // camera. Query camera to find all the sizes and choose the optimal size given the // dimensions of our preview surface. Camera.Parameters parameters = mCamera.getParameters(); List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes(); List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes(); Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes, mSupportedPreviewSizes, mPreview.getWidth(), mPreview.getHeight()); // Use the same size for recording profile. CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); profile.videoFrameWidth = optimalSize.width; profile.videoFrameHeight = optimalSize.height; // likewise for the camera object itself. parameters.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); mCamera.setParameters(parameters); try { // Requires API level 11+, For backward compatibility use {@link setPreviewDisplay} // with {@link SurfaceView} // mCamera.setPreviewTexture(mPreview.getSurfaceTexture()); mHolder = mPreview.getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // mHolder.setKeepScreenOn(true); setCameraDisplayOrientation(0); mCamera.setPreviewDisplay(mPreview.getHolder()); mCamera.startPreview(); Log.i(TAG, "Setting Preview Display"); } catch (IOException e) { Log.e(TAG, "Surface texture is unavailable or unsuitable" + e.getMessage()); return false; } // END_INCLUDE (configure_preview) return true; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private boolean prepareVideoRecorder() { if (!preparePreview()) { return false; } CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); // BEGIN_INCLUDE (configure_media_recorder) mMediaRecorder = new MediaRecorder(); // Step 1: Unlock and set camera to MediaRecorder mCamera.unlock(); mMediaRecorder.setCamera(mCamera); // Step 2: Set sources mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // Step 3: Set a CamcorderProfile (requires API Level 8 or higher) mMediaRecorder.setProfile(profile); // orientation int rotation = getWindowManager().getDefaultDisplay().getRotation(); Log.i("TESTING", "Sensor Orientation: " + sensorOrientation); if (sensorOrientation == SENSOR_ORIENTATION_DEFAULT_DEGREES) { Log.i("TESTING", "Recorder: DEFAULT ROTATION: " + DEFAULT_ORIENTATIONS.get(rotation)); mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); } else { Log.i("TESTING", "Recorder: INVERSING ROTATION: " + INVERSE_ORIENTATIONS.get(rotation)); mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); } // Step 4: Set output file mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO); if (mOutputFile == null) { return false; } mMediaRecorder.setOutputFile(mOutputFile.getPath()); // END_INCLUDE (configure_media_recorder) // Step 5: Prepare configured MediaRecorder try { mMediaRecorder.prepare(); } catch (IllegalStateException e) { Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage()); releaseMediaRecorder(); return false; } catch (IOException e) { Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage()); releaseMediaRecorder(); return false; } return true; } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { try { if (mCamera != null) { mCamera.setPreviewDisplay(mHolder); } } catch (IOException exception) { Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); } } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { Log.i("TESTING", "Surface changed called"); // If your preview can change or rotate, take care of those events here. // Make sure to stop the preview before resizing or reformatting it. if (mHolder.getSurface() == null) { Log.e("ERROR", "Preview Surface does not exist!"); return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (Exception e) { Log.e("ERROR", "Tried to stop a non-existent preview!"); } // set preview size and make any resize, rotate or // reformatting changes here // start preview with new settings try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); } catch (Exception e) { Log.d("ERROR", "Error starting mCamera preview: " + e.getMessage()); } } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { try { releaseCamera(); } catch (Exception e) { Log.e(TAG, "Camera release failure."); } } public void setCameraDisplayOrientation(int cameraId) { Log.i("TESTING", "Set Camera Display Orientation Called"); android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = getWindowManager().getDefaultDisplay().getRotation(); Log.i("TESTING", "Screen Rotation: " + rotation); Log.i("TESTING", "Sensor Orientation: " + sensorOrientation); if (sensorOrientation == SENSOR_ORIENTATION_DEFAULT_DEGREES) { Log.i("TESTING", "Camera: DEFAULT ROTATION: " + DEFAULT_ORIENTATIONS.get(rotation)); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { orientation = (info.orientation + degrees) % 360; orientation = (360 - orientation) % 360; // compensate the mirror Log.i("TESTING", "Front Facing Camera Orientation: " + orientation); } else { // back-facing orientation = (info.orientation - degrees + 360) % 360; Log.i("TESTING", "Back Facing Camera Orientation: " + orientation); } Log.i("TESTING", "Set Camera Display Orientation Finished"); } else { Log.i("TESTING", "Camera: INVERSING ROTATION: " + INVERSE_ORIENTATIONS.get(rotation)); orientation = INVERSE_ORIENTATIONS.get(rotation); } mCamera.setDisplayOrientation(orientation); } /** * Asynchronous task for preparing the {@link android.media.MediaRecorder} since it's a long blocking * operation. */ class MediaPrepareTask extends AsyncTask<Void, Void, Boolean> { @Override protected Boolean doInBackground(Void... voids) { // initialize video camera if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, // now you can start recording mMediaRecorder.start(); isRecording = true; } else { // prepare didn't work, release the camera releaseMediaRecorder(); return false; } return true; } @Override protected void onPostExecute(Boolean result) { if (!result) { MainActivity.this.finish(); } // inform the user that recording has started setCaptureButtonText("Stop"); } } public boolean checkForPermissions() { ArrayList<String> permissionsToGrant = new ArrayList<>(); // "Dangerous" Permissions: // http://stackoverflow.com/questions/36936914/list-of-android-permissions-normal-permissions-and-dangerous-permissions-in-api if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission( android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionsToGrant.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } else { Log.v("VERBOSE", "External storage permission is granted"); } if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { permissionsToGrant.add(Manifest.permission.CAMERA); } else { Log.v("VERBOSE", "Camera Permission granted"); } } // permission is automatically granted on sdk<23 upon installation Log.v("VERBOSE", "External storage permission is automatically granted"); if (permissionsToGrant.size() > 0) { ActivityCompat.requestPermissions(MainActivity.this, permissionsToGrant.toArray(new String[permissionsToGrant.size()]), REQUEST_ALL_PERMISSIONS); return false; } return true; } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { Log.i("TESTING", "onRequestPermissions Called"); switch (requestCode) { case REQUEST_ALL_PERMISSIONS: { Log.i("TESTING", "Permissions granted: Loading..."); Toast.makeText(this, "Permissions granted: Loading...", Toast.LENGTH_SHORT).show(); preparePreview(); return; } default: { Log.e("ERROR", "Permissions denied: Cannot load UI"); Toast.makeText(this, "Permissions denied: Cannot load UI", Toast.LENGTH_SHORT).show(); return; } } } }