1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.media.samplevideoencoder;
17 
18 import androidx.appcompat.app.AppCompatActivity;
19 import androidx.core.app.ActivityCompat;
20 
21 import android.Manifest;
22 import android.app.Activity;
23 
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 
27 import android.graphics.Matrix;
28 import android.graphics.RectF;
29 import android.hardware.camera2.CameraAccessException;
30 import android.hardware.camera2.CameraCaptureSession;
31 import android.hardware.camera2.CameraCharacteristics;
32 import android.hardware.camera2.CameraDevice;
33 import android.hardware.camera2.CameraManager;
34 import android.hardware.camera2.CameraMetadata;
35 import android.hardware.camera2.CaptureRequest;
36 import android.hardware.camera2.params.StreamConfigurationMap;
37 import android.graphics.SurfaceTexture;
38 import android.media.MediaCodecInfo;
39 import android.media.MediaFormat;
40 import android.media.MediaRecorder;
41 
42 import android.os.AsyncTask;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.HandlerThread;
47 import android.view.Surface;
48 import android.view.View;
49 import android.view.TextureView;
50 import android.widget.Button;
51 import android.widget.CheckBox;
52 
53 import java.io.File;
54 import java.io.IOException;
55 
56 import android.util.Log;
57 import android.util.Size;
58 import android.widget.RadioGroup;
59 import android.widget.TextView;
60 import android.widget.Toast;
61 
62 import java.lang.ref.WeakReference;
63 import java.util.ArrayList;
64 import java.util.List;
65 import java.util.Collections;
66 import java.util.Comparator;
67 import java.util.concurrent.Semaphore;
68 import java.util.concurrent.TimeUnit;
69 
70 import static java.lang.Boolean.FALSE;
71 import static java.lang.Boolean.TRUE;
72 
73 public class MainActivity extends AppCompatActivity
74         implements View.OnClickListener, ActivityCompat.OnRequestPermissionsResultCallback {
75 
76     private static final String TAG = "SampleVideoEncoder";
77     private static final String[] RECORD_PERMISSIONS =
78             {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
79     private static final int REQUEST_RECORD_PERMISSIONS = 1;
80     private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
81     private static final int VIDEO_BITRATE = 8000000 /* 8 Mbps */;
82     private static final int VIDEO_FRAMERATE = 30;
83 
84     /**
85      * Constant values to frame types assigned here are internal to this app.
86      * These values does not correspond to the actual values defined in avc/hevc specifications.
87      */
88     public static final int FRAME_TYPE_I = 0;
89     public static final int FRAME_TYPE_P = 1;
90     public static final int FRAME_TYPE_B = 2;
91 
92     private String mMime = MediaFormat.MIMETYPE_VIDEO_AVC;
93     private String mOutputVideoPath = null;
94 
95     private final boolean mIsFrontCamera = true;
96     private boolean mIsCodecSoftware = false;
97     private boolean mIsMediaRecorder = true;
98     private boolean mIsRecording;
99 
100     private AutoFitTextureView mTextureView;
101     private TextView mTextView;
102     private CameraDevice mCameraDevice;
103     private CameraCaptureSession mPreviewSession;
104     private CaptureRequest.Builder mPreviewBuilder;
105     private MediaRecorder mMediaRecorder;
106     private Size mVideoSize;
107     private Size mPreviewSize;
108 
109     private Handler mBackgroundHandler;
110     private HandlerThread mBackgroundThread;
111 
112     private Button mStartButton;
113 
114     private int[] mFrameTypeOccurrences;
115 
116     @Override
onCreate(Bundle savedInstanceState)117     protected void onCreate(Bundle savedInstanceState) {
118         super.onCreate(savedInstanceState);
119         setContentView(R.layout.activity_main);
120 
121         final RadioGroup radioGroup_mime = findViewById(R.id.radio_group_mime);
122         radioGroup_mime.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
123             @Override
124             public void onCheckedChanged(RadioGroup group, int checkedId) {
125                 if (checkedId == R.id.avc) {
126                     mMime = MediaFormat.MIMETYPE_VIDEO_AVC;
127                 } else {
128                     mMime = MediaFormat.MIMETYPE_VIDEO_HEVC;
129                 }
130             }
131         });
132 
133         final RadioGroup radioGroup_codec = findViewById(R.id.radio_group_codec);
134         radioGroup_codec.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
135             @Override
136             public void onCheckedChanged(RadioGroup group, int checkedId) {
137                 mIsCodecSoftware = checkedId == R.id.sw;
138             }
139         });
140 
141         final CheckBox checkBox_mr = findViewById(R.id.checkBox_media_recorder);
142         final CheckBox checkBox_mc = findViewById(R.id.checkBox_media_codec);
143         mTextureView = findViewById(R.id.texture);
144         mTextView = findViewById(R.id.textViewResults);
145 
146         checkBox_mr.setOnClickListener(new View.OnClickListener() {
147             @Override
148             public void onClick(View v) {
149                 boolean checked = ((CheckBox) v).isChecked();
150                 if (checked) {
151                     checkBox_mc.setChecked(false);
152                     mIsMediaRecorder = TRUE;
153                     for (int i = 0; i < radioGroup_codec.getChildCount(); i++) {
154                         radioGroup_codec.getChildAt(i).setEnabled(false);
155                     }
156                 }
157             }
158         });
159         checkBox_mc.setOnClickListener(new View.OnClickListener() {
160             @Override
161             public void onClick(View v) {
162                 boolean checked = ((CheckBox) v).isChecked();
163                 if (checked) {
164                     checkBox_mr.setChecked(false);
165                     mIsMediaRecorder = FALSE;
166                     for (int i = 0; i < radioGroup_codec.getChildCount(); i++) {
167                         radioGroup_codec.getChildAt(i).setEnabled(true);
168                     }
169                 }
170             }
171         });
172         mStartButton = findViewById(R.id.start_button);
173         mStartButton.setOnClickListener(this);
174     }
175 
176     @Override
onClick(View v)177     public void onClick(View v) {
178         if (v.getId() == R.id.start_button) {
179             mTextView.setText(null);
180             if (mIsMediaRecorder) {
181                 if (mIsRecording) {
182                     stopRecordingVideo();
183                 } else {
184                     mStartButton.setEnabled(false);
185                     startRecordingVideo();
186                 }
187             } else {
188                 mStartButton.setEnabled(false);
189                 mOutputVideoPath = getVideoPath(MainActivity.this);
190                 MediaCodecSurfaceAsync codecAsyncTask = new MediaCodecSurfaceAsync(this);
191                 codecAsyncTask.execute(
192                         "Encoding reference test vector with MediaCodec APIs using surface");
193             }
194         }
195     }
196 
197     private static class MediaCodecSurfaceAsync extends AsyncTask<String, String, Integer> {
198 
199         private final WeakReference<MainActivity> activityReference;
200 
MediaCodecSurfaceAsync(MainActivity context)201         MediaCodecSurfaceAsync(MainActivity context) {
202             activityReference = new WeakReference<>(context);
203         }
204 
205         @Override
doInBackground(String... strings)206         protected Integer doInBackground(String... strings) {
207             MainActivity mainActivity = activityReference.get();
208             int resId = R.raw.crowd_1920x1080_25fps_4000kbps_h265;
209             int encodingStatus = 1;
210             MediaCodecSurfaceEncoder codecSurfaceEncoder =
211                     new MediaCodecSurfaceEncoder(mainActivity.getApplicationContext(), resId,
212                             mainActivity.mMime, mainActivity.mIsCodecSoftware,
213                             mainActivity.mOutputVideoPath);
214             try {
215                 encodingStatus = codecSurfaceEncoder.startEncodingSurface();
216                 mainActivity.mFrameTypeOccurrences = codecSurfaceEncoder.getFrameTypes();
217             } catch (IOException | InterruptedException e) {
218                 e.printStackTrace();
219             }
220             return encodingStatus;
221         }
222 
223         @Override
onPostExecute(Integer encodingStatus)224         protected void onPostExecute(Integer encodingStatus) {
225             MainActivity mainActivity = activityReference.get();
226             mainActivity.mStartButton.setEnabled(true);
227             if (encodingStatus == 0) {
228                 Toast.makeText(mainActivity.getApplicationContext(), "Encoding Completed",
229                         Toast.LENGTH_SHORT).show();
230                 mainActivity.mTextView.append("\n Encoded stream contains: ");
231                 mainActivity.mTextView.append("\n Number of I-Frames: " +
232                         mainActivity.mFrameTypeOccurrences[FRAME_TYPE_I]);
233                 mainActivity.mTextView.append("\n Number of P-Frames: " +
234                         mainActivity.mFrameTypeOccurrences[FRAME_TYPE_P]);
235                 mainActivity.mTextView.append("\n Number of B-Frames: " +
236                         mainActivity.mFrameTypeOccurrences[FRAME_TYPE_B]);
237             } else {
238                 Toast.makeText(mainActivity.getApplicationContext(),
239                         "Error occurred while " + "encoding", Toast.LENGTH_SHORT).show();
240             }
241             mainActivity.mOutputVideoPath = null;
242             super.onPostExecute(encodingStatus);
243         }
244     }
245 
246     private final TextureView.SurfaceTextureListener mSurfaceTextureListener =
247             new TextureView.SurfaceTextureListener() {
248 
249                 @Override
250                 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
251                                                       int height) {
252                     openCamera(width, height);
253                 }
254 
255                 @Override
256                 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
257                                                         int height) {
258                     configureTransform(width, height);
259                     Log.v(TAG, "Keeping camera preview size fixed");
260                 }
261 
262                 @Override
263                 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
264                     return true;
265                 }
266 
267                 @Override
268                 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
269                 }
270             };
271 
272 
273     private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
274 
275         @Override
276         public void onOpened(CameraDevice cameraDevice) {
277             mCameraDevice = cameraDevice;
278             startPreview();
279             mCameraOpenCloseLock.release();
280         }
281 
282         @Override
283         public void onDisconnected(CameraDevice cameraDevice) {
284             mCameraOpenCloseLock.release();
285             cameraDevice.close();
286             mCameraDevice = null;
287         }
288 
289         @Override
290         public void onError(CameraDevice cameraDevice, int error) {
291             mCameraOpenCloseLock.release();
292             cameraDevice.close();
293             mCameraDevice = null;
294             Activity activity = MainActivity.this;
295             activity.finish();
296         }
297     };
298 
shouldShowRequestPermissionRationale(String[] recordPermissions)299     private boolean shouldShowRequestPermissionRationale(String[] recordPermissions) {
300         for (String permission : recordPermissions) {
301             if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
302                 return true;
303             }
304         }
305         return false;
306     }
307 
requestRecordPermissions()308     private void requestRecordPermissions() {
309         if (!shouldShowRequestPermissionRationale(RECORD_PERMISSIONS)) {
310             ActivityCompat.requestPermissions(this, RECORD_PERMISSIONS, REQUEST_RECORD_PERMISSIONS);
311         }
312     }
313 
314     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)315     public void onRequestPermissionsResult(int requestCode, String[] permissions,
316                                            int[] grantResults) {
317         if (requestCode == REQUEST_RECORD_PERMISSIONS) {
318             if (grantResults.length == RECORD_PERMISSIONS.length) {
319                 for (int result : grantResults) {
320                     if (result != PackageManager.PERMISSION_GRANTED) {
321                         Log.e(TAG, "Permission is not granted");
322                         break;
323                     }
324                 }
325             }
326         } else {
327             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
328         }
329     }
330 
331     @SuppressWarnings("MissingPermission")
openCamera(int width, int height)332     private void openCamera(int width, int height) {
333         if (!hasPermissionGranted(RECORD_PERMISSIONS)) {
334             Log.e(TAG, "Camera does not have permission to record video");
335             requestRecordPermissions();
336             return;
337         }
338         final Activity activity = MainActivity.this;
339         if (activity == null || activity.isFinishing()) {
340             Log.e(TAG, "Activity not found");
341             return;
342         }
343         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
344         try {
345             Log.v(TAG, "Acquire Camera");
346             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
347                 throw new RuntimeException("Timed out waiting to lock camera opening");
348             }
349             Log.d(TAG, "Camera Acquired");
350 
351             String cameraId = manager.getCameraIdList()[0];
352             if (mIsFrontCamera) {
353                 cameraId = manager.getCameraIdList()[1];
354             }
355 
356             CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
357             StreamConfigurationMap map =
358                     characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
359             mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
360             mPreviewSize =
361                     chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height,
362                             mVideoSize);
363             mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
364             configureTransform(width, height);
365             mMediaRecorder = new MediaRecorder();
366             manager.openCamera(cameraId, mStateCallback, null);
367         } catch (InterruptedException | CameraAccessException e) {
368             e.printStackTrace();
369         }
370     }
371 
closeCamera()372     private void closeCamera() {
373         try {
374             mCameraOpenCloseLock.acquire();
375             closePreviewSession();
376             if (null != mCameraDevice) {
377                 mCameraDevice.close();
378                 mCameraDevice = null;
379             }
380             if (null != mMediaRecorder) {
381                 mMediaRecorder.release();
382                 mMediaRecorder = null;
383             }
384         } catch (InterruptedException e) {
385             throw new RuntimeException("Interrupted while trying to lock camera closing.");
386         } finally {
387             mCameraOpenCloseLock.release();
388         }
389     }
390 
chooseVideoSize(Size[] choices)391     private static Size chooseVideoSize(Size[] choices) {
392         for (Size size : choices) {
393             if (size.getWidth() == size.getHeight() * 16 / 9 && size.getWidth() <= 1920) {
394                 return size;
395             }
396         }
397         Log.e(TAG, "Couldn't find any suitable video size");
398         return choices[choices.length - 1];
399     }
400 
chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio)401     private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
402         List<Size> bigEnough = new ArrayList<>();
403         int w = aspectRatio.getWidth();
404         int h = aspectRatio.getHeight();
405         for (Size option : choices) {
406             if (option.getHeight() == option.getWidth() * h / w && option.getWidth() >= width &&
407                     option.getHeight() >= height) {
408                 bigEnough.add(option);
409             }
410         }
411 
412         // Pick the smallest of those, assuming we found any
413         if (bigEnough.size() > 0) {
414             return Collections.min(bigEnough, new CompareSizesByArea());
415         } else {
416             Log.e(TAG, "Couldn't find any suitable preview size");
417             return choices[0];
418         }
419     }
420 
hasPermissionGranted(String[] recordPermissions)421     private boolean hasPermissionGranted(String[] recordPermissions) {
422         for (String permission : recordPermissions) {
423             if (ActivityCompat.checkSelfPermission(MainActivity.this, permission) !=
424                     PackageManager.PERMISSION_GRANTED) {
425                 return false;
426             }
427         }
428         return true;
429     }
430 
431     @Override
onResume()432     public void onResume() {
433         super.onResume();
434         startBackgroundThread();
435         if (mTextureView.isAvailable()) {
436             openCamera(mTextureView.getWidth(), mTextureView.getHeight());
437         } else {
438             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
439         }
440     }
441 
442     @Override
onPause()443     public void onPause() {
444         closeCamera();
445         stopBackgroundThread();
446         super.onPause();
447     }
448 
startBackgroundThread()449     private void startBackgroundThread() {
450         mBackgroundThread = new HandlerThread("CameraBackground");
451         mBackgroundThread.start();
452         mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
453     }
454 
stopBackgroundThread()455     private void stopBackgroundThread() {
456         mBackgroundThread.quitSafely();
457         try {
458             mBackgroundThread.join();
459             mBackgroundThread = null;
460             mBackgroundHandler = null;
461         } catch (InterruptedException e) {
462             e.printStackTrace();
463         }
464     }
465 
startRecordingVideo()466     private void startRecordingVideo() {
467         if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
468             Toast.makeText(MainActivity.this, "Cannot start recording.", Toast.LENGTH_SHORT).show();
469             Log.e(TAG, "Cannot start recording.");
470             return;
471         }
472         try {
473             closePreviewSession();
474             setUpMediaRecorder();
475             SurfaceTexture texture = mTextureView.getSurfaceTexture();
476             assert texture != null;
477             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
478             mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
479             List<Surface> surfaces = new ArrayList<>();
480 
481             // Set up Surface for the camera preview
482             Surface previewSurface = new Surface(texture);
483             surfaces.add(previewSurface);
484             mPreviewBuilder.addTarget(previewSurface);
485 
486             // Set up Surface for the MediaRecorder
487             Surface recorderSurface = mMediaRecorder.getSurface();
488             surfaces.add(recorderSurface);
489             mPreviewBuilder.addTarget(recorderSurface);
490 
491             //Start a capture session
492             mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
493 
494                 @Override
495                 public void onConfigured(CameraCaptureSession session) {
496                     mPreviewSession = session;
497                     updatePreview();
498                     MainActivity.this.runOnUiThread(new Runnable() {
499                         @Override
500                         public void run() {
501                             mIsRecording = true;
502                             mMediaRecorder.start();
503                             mStartButton.setText(R.string.stop);
504                             mStartButton.setEnabled(true);
505                         }
506                     });
507                 }
508 
509                 @Override
510                 public void onConfigureFailed(CameraCaptureSession session) {
511                     Log.e(TAG, "Failed to configure. Cannot start Recording");
512                 }
513             }, mBackgroundHandler);
514         } catch (CameraAccessException e) {
515             e.printStackTrace();
516         }
517     }
518 
setUpMediaRecorder()519     private void setUpMediaRecorder() {
520         final Activity activity = MainActivity.this;
521         if (activity == null) {
522             Toast.makeText(MainActivity.this, "Error occurred while setting up the MediaRecorder",
523                     Toast.LENGTH_SHORT).show();
524             Log.e(TAG, "Error occurred while setting up the MediaRecorder");
525             return;
526         }
527         try {
528             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
529             mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
530             mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
531         } catch (IllegalStateException e) {
532             e.printStackTrace();
533         }
534         if (mOutputVideoPath == null) {
535             mOutputVideoPath = getVideoPath(MainActivity.this);
536         }
537         mMediaRecorder.setOutputFile(mOutputVideoPath);
538         mMediaRecorder.setVideoEncodingBitRate(VIDEO_BITRATE);
539         mMediaRecorder.setVideoFrameRate(VIDEO_FRAMERATE);
540         mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
541         mMediaRecorder.setOrientationHint(270);
542         if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
543             mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.HEVC);
544             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
545                 mMediaRecorder.setVideoEncodingProfileLevel(
546                         MediaCodecInfo.CodecProfileLevel.HEVCProfileMain,
547                         MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4);
548             }
549         } else {
550             mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
551             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
552                 mMediaRecorder.setVideoEncodingProfileLevel(
553                         MediaCodecInfo.CodecProfileLevel.AVCProfileMain,
554                         MediaCodecInfo.CodecProfileLevel.AVCLevel4);
555             }
556         }
557         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
558         try {
559             mMediaRecorder.prepare();
560         } catch (IOException e) {
561             e.printStackTrace();
562         }
563     }
564 
getVideoPath(Activity activity)565     private String getVideoPath(Activity activity) {
566         File dir = activity.getApplicationContext().getExternalFilesDir(null);
567         if (dir == null) {
568             Log.e(TAG, "Cannot get external directory path to save output video");
569             return null;
570         }
571         String videoPath = dir.getAbsolutePath() + "/Video-" + System.currentTimeMillis() + ".mp4";
572         Log.d(TAG, "Output video is saved at: " + videoPath);
573         return videoPath;
574     }
575 
closePreviewSession()576     private void closePreviewSession() {
577         if (mPreviewSession != null) {
578             mPreviewSession.close();
579             mPreviewSession = null;
580         }
581     }
582 
stopRecordingVideo()583     private void stopRecordingVideo() {
584         mIsRecording = false;
585         mStartButton.setText(R.string.start);
586         mMediaRecorder.stop();
587         mMediaRecorder.reset();
588         Toast.makeText(MainActivity.this, "Recording Finished", Toast.LENGTH_SHORT).show();
589         mOutputVideoPath = null;
590         startPreview();
591     }
592 
startPreview()593     private void startPreview() {
594         if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
595             return;
596         }
597         try {
598             closePreviewSession();
599             SurfaceTexture texture = mTextureView.getSurfaceTexture();
600             assert texture != null;
601             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
602             mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
603 
604             Surface previewSurface = new Surface(texture);
605             mPreviewBuilder.addTarget(previewSurface);
606 
607             mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
608                     new CameraCaptureSession.StateCallback() {
609 
610                         @Override
611                         public void onConfigured(CameraCaptureSession session) {
612                             mPreviewSession = session;
613                             updatePreview();
614                         }
615 
616                         @Override
617                         public void onConfigureFailed(CameraCaptureSession session) {
618                             Toast.makeText(MainActivity.this,
619                                     "Configure Failed; Cannot start " + "preview",
620                                     Toast.LENGTH_SHORT).show();
621                             Log.e(TAG, "Configure failed; Cannot start preview");
622                         }
623                     }, mBackgroundHandler);
624         } catch (CameraAccessException e) {
625             e.printStackTrace();
626         }
627     }
628 
updatePreview()629     private void updatePreview() {
630         if (mCameraDevice == null) {
631             Toast.makeText(MainActivity.this, "Camera not found; Cannot update " + "preview",
632                     Toast.LENGTH_SHORT).show();
633             Log.e(TAG, "Camera not found; Cannot update preview");
634             return;
635         }
636         try {
637             mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
638             HandlerThread thread = new HandlerThread("Camera preview");
639             thread.start();
640             mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
641         } catch (CameraAccessException e) {
642             e.printStackTrace();
643         }
644     }
645 
configureTransform(int viewWidth, int viewHeight)646     private void configureTransform(int viewWidth, int viewHeight) {
647         Activity activity = MainActivity.this;
648         if (null == mTextureView || null == mPreviewSize || null == activity) {
649             return;
650         }
651         Matrix matrix = new Matrix();
652         RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
653         RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
654         float centerX = viewRect.centerX();
655         float centerY = viewRect.centerY();
656         bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
657         matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
658         float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),
659                 (float) viewWidth / mPreviewSize.getWidth());
660         matrix.postScale(scale, scale, centerX, centerY);
661         mTextureView.setTransform(matrix);
662     }
663 
664     static class CompareSizesByArea implements Comparator<Size> {
665         @Override
compare(Size lhs, Size rhs)666         public int compare(Size lhs, Size rhs) {
667             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
668                     (long) rhs.getWidth() * rhs.getHeight());
669         }
670     }
671 }
672