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