Created
March 19, 2015 10:19
-
-
Save anonymous/be03bb6f5fa5287bc52b to your computer and use it in GitHub Desktop.
fix for Camera2Video from google's sample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint; | |
import android.app.Activity; | |
import android.app.AlertDialog; | |
import android.app.Dialog; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.res.Configuration; | |
import android.graphics.Matrix; | |
import android.graphics.RectF; | |
import android.graphics.SurfaceTexture; | |
import android.hardware.camera2.CameraAccessException; | |
import android.hardware.camera2.CameraCaptureSession; | |
import android.hardware.camera2.CameraCharacteristics; | |
import android.hardware.camera2.CameraDevice; | |
import android.hardware.camera2.CameraManager; | |
import android.hardware.camera2.CameraMetadata; | |
import android.hardware.camera2.CaptureRequest; | |
import android.hardware.camera2.params.StreamConfigurationMap; | |
import android.media.MediaRecorder; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.HandlerThread; | |
import android.support.v4.app.Fragment; | |
import android.util.Log; | |
import android.util.Size; | |
import android.util.SparseIntArray; | |
import android.view.LayoutInflater; | |
import android.view.Surface; | |
import android.view.TextureView; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.Button; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.List; | |
import java.util.concurrent.Semaphore; | |
import java.util.concurrent.TimeUnit; | |
@SuppressLint("NewApi") | |
public class Camera2VideoFragment extends Fragment { | |
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); | |
private static final String TAG = "Camera2VideoFragment"; | |
static { | |
ORIENTATIONS.append(Surface.ROTATION_0, 90); | |
ORIENTATIONS.append(Surface.ROTATION_90, 0); | |
ORIENTATIONS.append(Surface.ROTATION_180, 270); | |
ORIENTATIONS.append(Surface.ROTATION_270, 180); | |
} | |
private Integer facing = null; | |
/** | |
* An {@link AutoFitTextureView} for camera preview. | |
*/ | |
private AutoFitTextureView mTextureView; | |
/** | |
* Button to record video | |
*/ | |
private Button mButtonVideo; | |
/** | |
* A refernce to the opened {@link android.hardware.camera2.CameraDevice}. | |
*/ | |
private CameraDevice mCameraDevice; | |
/** | |
* A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for preview. | |
*/ | |
private CameraCaptureSession mPreviewSession; | |
/** | |
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a | |
* {@link TextureView}. | |
*/ | |
private TextureView.SurfaceTextureListener mSurfaceTextureListener | |
= new TextureView.SurfaceTextureListener() { | |
@Override | |
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
openCamera(width, height); | |
} | |
@Override | |
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
configureTransform(width, height); | |
} | |
@Override | |
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { | |
return true; | |
} | |
@Override | |
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { | |
} | |
}; | |
/** | |
* The {@link android.util.Size} of camera preview. | |
*/ | |
private Size mPreviewSize; | |
/** | |
* The {@link android.util.Size} of video recording. | |
*/ | |
private Size mVideoSize; | |
/** | |
* Camera preview. | |
*/ | |
private CaptureRequest.Builder mPreviewBuilder; | |
/** | |
* MediaRecorder | |
*/ | |
private MediaRecorder mMediaRecorder; | |
/** | |
* Whether the app is recording video now | |
*/ | |
private boolean mIsRecordingVideo; | |
/** | |
* An additional thread for running tasks that shouldn't block the UI. | |
*/ | |
private HandlerThread mBackgroundThread; | |
/** | |
* A {@link Handler} for running tasks in the background. | |
*/ | |
private Handler mBackgroundHandler; | |
/** | |
* A {@link Semaphore} to prevent the app from exiting before closing the camera. | |
*/ | |
private Semaphore mCameraOpenCloseLock = new Semaphore(1); | |
/** | |
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status. | |
*/ | |
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { | |
@Override | |
public void onOpened(CameraDevice cameraDevice) { | |
mCameraDevice = cameraDevice; | |
startPreview(); | |
mCameraOpenCloseLock.release(); | |
if (null != mTextureView) { | |
configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); | |
} | |
} | |
@Override | |
public void onDisconnected(CameraDevice cameraDevice) { | |
mCameraOpenCloseLock.release(); | |
cameraDevice.close(); | |
mCameraDevice = null; | |
} | |
@Override | |
public void onError(CameraDevice cameraDevice, int error) { | |
mCameraOpenCloseLock.release(); | |
cameraDevice.close(); | |
mCameraDevice = null; | |
Activity activity = getActivity(); | |
if (null != activity) { | |
activity.finish(); | |
} | |
} | |
}; | |
public static Camera2VideoFragment newInstance() { | |
Camera2VideoFragment fragment = new Camera2VideoFragment(); | |
fragment.setRetainInstance(true); | |
return fragment; | |
} | |
/** | |
* In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes larger | |
* than 1080p, since MediaRecorder cannot handle such a high-resolution video. | |
* | |
* @param choices The list of available sizes | |
* @return The video size | |
*/ | |
private static Size chooseVideoSize(Size[] choices) { | |
for (Size size : choices) { | |
if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) { | |
return size; | |
} | |
} | |
Log.e(TAG, "Couldn't find any suitable video size"); | |
return choices[choices.length - 1]; | |
} | |
/** | |
* Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose | |
* width and height are at least as large as the respective requested values, and whose aspect | |
* ratio matches with the specified value. | |
* | |
* @param choices The list of sizes that the camera supports for the intended output class | |
* @param width The minimum desired width | |
* @param height The minimum desired height | |
* @param aspectRatio The aspect ratio | |
* @return The optimal {@code Size}, or an arbitrary one if none were big enough | |
*/ | |
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) { | |
// Collect the supported resolutions that are at least as big as the preview Surface | |
List<Size> bigEnough = new ArrayList<Size>(); | |
int w = aspectRatio.getWidth(); | |
int h = aspectRatio.getHeight(); | |
for (Size option : choices) { | |
if (option.getHeight() == option.getWidth() * h / w && | |
option.getWidth() >= width && option.getHeight() >= height) { | |
bigEnough.add(option); | |
} | |
} | |
// Pick the smallest of those, assuming we found any | |
if (bigEnough.size() > 0) { | |
return Collections.min(bigEnough, new CompareSizesByArea()); | |
} else { | |
Log.e(TAG, "Couldn't find any suitable preview size"); | |
return choices[0]; | |
} | |
} | |
@Override | |
public View onCreateView(LayoutInflater inflater, ViewGroup container, | |
Bundle savedInstanceState) { | |
return inflater.inflate(R.layout.fragment_camera2_video, container, false); | |
} | |
@Override | |
public void onViewCreated(final View view, Bundle savedInstanceState) { | |
mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); | |
} | |
@Override | |
public void onResume() { | |
super.onResume(); | |
startBackgroundThread(); | |
if (mTextureView.isAvailable()) { | |
openCamera(mTextureView.getWidth(), mTextureView.getHeight()); | |
} else { | |
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); | |
} | |
} | |
@Override | |
public void onPause() { | |
closeCamera(); | |
stopBackgroundThread(); | |
super.onPause(); | |
} | |
/** | |
* Starts a background thread and its {@link Handler}. | |
*/ | |
private void startBackgroundThread() { | |
mBackgroundThread = new HandlerThread("CameraBackground"); | |
mBackgroundThread.start(); | |
mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); | |
} | |
/** | |
* Stops the background thread and its {@link Handler}. | |
*/ | |
private void stopBackgroundThread() { | |
mBackgroundThread.quitSafely(); | |
try { | |
mBackgroundThread.join(); | |
mBackgroundThread = null; | |
mBackgroundHandler = null; | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`. | |
*/ | |
private void openCamera(int width, int height) { | |
final Activity activity = getActivity(); | |
if (null == activity || activity.isFinishing()) { | |
return; | |
} | |
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); | |
try { | |
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { | |
throw new RuntimeException("Time out waiting to lock camera opening."); | |
} | |
String idForOpen = null; | |
String[] camerasIds = manager.getCameraIdList(); | |
for(String id : camerasIds){ | |
CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(id); | |
int facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); | |
if(this.facing == null){ | |
if(facing == CameraMetadata.LENS_FACING_BACK){ | |
idForOpen = id; | |
break; | |
} | |
} else { | |
if(this.facing == facing){ | |
idForOpen = id; | |
break; | |
} | |
} | |
} | |
String cameraId; | |
if(idForOpen != null){ | |
cameraId = idForOpen; | |
} else { | |
cameraId = manager.getCameraIdList()[0]; | |
} | |
// Choose the sizes for camera preview and video recording | |
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); | |
facing = characteristics.get(CameraCharacteristics.LENS_FACING); | |
StreamConfigurationMap map = characteristics | |
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class)); | |
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), | |
width, height, mVideoSize); | |
int orientation = getResources().getConfiguration().orientation; | |
if (orientation == Configuration.ORIENTATION_LANDSCAPE) { | |
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); | |
} else { | |
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); | |
} | |
configureTransform(width, height); | |
mMediaRecorder = new MediaRecorder(); | |
manager.openCamera(cameraId, mStateCallback, null); | |
} catch (CameraAccessException e) { | |
Log.e("videoFragment", "Cannot access the camera"); | |
// Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show(); | |
activity.finish(); | |
} catch (NullPointerException e) { | |
// Currently an NPE is thrown when the Camera2API is used but not supported on the | |
// device this code runs. | |
// new ErrorDialog().show(getFragmentManager(), "dialog"); | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Interrupted while trying to lock camera opening."); | |
} | |
} | |
private void closeCamera() { | |
try { | |
mCameraOpenCloseLock.acquire(); | |
if (null != mCameraDevice) { | |
mCameraDevice.close(); | |
mCameraDevice = null; | |
} | |
if (null != mMediaRecorder) { | |
mMediaRecorder.release(); | |
mMediaRecorder = null; | |
} | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Interrupted while trying to lock camera closing."); | |
} finally { | |
mCameraOpenCloseLock.release(); | |
} | |
} | |
/** | |
* Start the camera preview. | |
*/ | |
private void startPreview() { | |
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) { | |
return; | |
} | |
try { | |
setUpMediaRecorder(); | |
SurfaceTexture texture = mTextureView.getSurfaceTexture(); | |
assert texture != null; | |
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); | |
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); | |
List<Surface> surfaces = new ArrayList<Surface>(); | |
Surface previewSurface = new Surface(texture); | |
surfaces.add(previewSurface); | |
mPreviewBuilder.addTarget(previewSurface); | |
Surface recorderSurface = mMediaRecorder.getSurface(); | |
surfaces.add(recorderSurface); | |
mPreviewBuilder.addTarget(recorderSurface); | |
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { | |
@Override | |
public void onConfigured(CameraCaptureSession cameraCaptureSession) { | |
mPreviewSession = cameraCaptureSession; | |
updatePreview(); | |
startRecordingVideo();// auto start! | |
} | |
@Override | |
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { | |
Activity activity = getActivity(); | |
if (null != activity) { | |
Log.e("videoFragment", "Failed"); | |
// Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); | |
} | |
} | |
}, mBackgroundHandler); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Update the camera preview. {@link #startPreview()} needs to be called in advance. | |
*/ | |
private void updatePreview() { | |
if (null == mCameraDevice) { | |
return; | |
} | |
try { | |
setUpCaptureRequestBuilder(mPreviewBuilder); | |
HandlerThread thread = new HandlerThread("CameraPreview"); | |
thread.start(); | |
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) { | |
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); | |
} | |
/** | |
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. | |
* This method should not to be called until the camera preview size is determined in | |
* openCamera, or until the size of `mTextureView` is fixed. | |
* | |
* @param viewWidth The width of `mTextureView` | |
* @param viewHeight The height of `mTextureView` | |
*/ | |
private void configureTransform(int viewWidth, int viewHeight) { | |
Activity activity = getActivity(); | |
if (null == mTextureView || null == mPreviewSize || null == activity) { | |
return; | |
} | |
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); | |
Matrix matrix = new Matrix(); | |
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); | |
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.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 / mPreviewSize.getHeight(), | |
(float) viewWidth / mPreviewSize.getWidth()); | |
matrix.postScale(scale, scale, centerX, centerY); | |
matrix.postRotate(90 * (rotation - 2), centerX, centerY); | |
} | |
mTextureView.setTransform(matrix); | |
} | |
private void setUpMediaRecorder() throws IOException { | |
final Activity activity = getActivity(); | |
if (null == activity) { | |
return; | |
} | |
// mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); | |
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); | |
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); | |
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); | |
mMediaRecorder.setOutputFile(getVideoFile(activity).getAbsolutePath()); | |
mMediaRecorder.setVideoEncodingBitRate(10000000); | |
mMediaRecorder.setVideoFrameRate(30); | |
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); | |
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); | |
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); | |
// mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); | |
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); | |
int orientation = ORIENTATIONS.get(rotation); | |
mMediaRecorder.setOrientationHint(orientation); | |
mMediaRecorder.prepare(); | |
} | |
private File getVideoFile(Context context) { | |
return new File(context.getExternalFilesDir(null), "video.mp4"); | |
} | |
private void startRecordingVideo() { | |
try { | |
// UI | |
mIsRecordingVideo = true; | |
// Start recording | |
mMediaRecorder.start(); | |
} catch (IllegalStateException e) { | |
e.printStackTrace(); | |
} | |
} | |
// OLD code: | |
// private void stopRecordingVideo() { | |
// // UI | |
// mIsRecordingVideo = false; | |
// mButtonVideo.setText(R.string.record); | |
// // Stop recording | |
// mMediaRecorder.stop(); | |
// mMediaRecorder.reset(); | |
// Activity activity = getActivity(); | |
// if (null != activity) { | |
// Toast.makeText(activity, "Video saved: " + getVideoFile(activity), | |
// Toast.LENGTH_SHORT).show(); | |
// } | |
// startPreview(); | |
// } | |
private void stopRecordingVideo(boolean start) { | |
// UI | |
mIsRecordingVideo = false; | |
closeCamera(); | |
if(start) | |
openCamera(mTextureView.getWidth(), mTextureView.getHeight()); | |
} | |
/** | |
* Compares two {@code Size}s based on their areas. | |
*/ | |
static class CompareSizesByArea implements Comparator<Size> { | |
@Override | |
public int compare(Size lhs, Size rhs) { | |
// We cast here to ensure the multiplications won't overflow | |
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - | |
(long) rhs.getWidth() * rhs.getHeight()); | |
} | |
} | |
public boolean canSwitch() { | |
CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); | |
try { | |
int count = manager.getCameraIdList().length; | |
return count > 1; | |
} catch (CameraAccessException e) { | |
return false; | |
} | |
} | |
public void switchCamera() { | |
if(!canSwitch()){ | |
return; | |
} | |
if(facing == CameraMetadata.LENS_FACING_BACK){ | |
facing = CameraMetadata.LENS_FACING_FRONT; | |
} else if(facing == CameraMetadata.LENS_FACING_FRONT){ | |
facing = CameraMetadata.LENS_FACING_BACK; | |
} | |
stopRecordingVideo(true); // and automatically start | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment