package com.eighteengray.procameralibrary.camera;
import android.content.Context;
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.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaRecorder;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.widget.Toast;
import com.eighteengray.procameralibrary.dataevent.RecordVideoEvent;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.eighteengray.commonutillibrary.SDCardUtils.getSystemPicFile;
public class RecordTextureView extends BaseCamera2TextureView
{
private Size mVideoSize;
private MediaRecorder mMediaRecorder;
private String mNextVideoAbsolutePath;
private CaptureRequest.Builder mPreviewRequestBuilder;
private CaptureRequest.Builder mRecordVideoBuilder;
private boolean isRecording;
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);
}
public RecordTextureView(Context context)
{
super(context);
}
public RecordTextureView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public RecordTextureView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
//******************************************************************************************
// public 方法,供外部调用
//********************************************************************************************
public void startRecordVideo()
{
if (null == mCameraDevice || !isAvailable() || null == mPreviewSize)
{
return;
}
try
{
isRecording = true;
closePreviewSession();
configureMediaRecorder();
List<Surface> surfaces = new ArrayList<>();
surfaces.add(surface);
surfaces.add(mMediaRecorder.getSurface());
mRecordVideoBuilder = CaptureRequestFactory.createRecordBuilder(mCameraDevice, surfaces);
mCameraDevice.createCaptureSession(surfaces, recordSessionStateCallback, mBackgroundHandler);
} catch (CameraAccessException e)
{
e.printStackTrace();
}
}
public void stopRecordVideo()
{
isRecording = false;
//EventBus发送消息,更新UI
RecordVideoEvent recordVideoEvent = new RecordVideoEvent();
recordVideoEvent.setRecording(false);
EventBus.getDefault().post(recordVideoEvent);
try
{
//下面三个参数必须加,不加的话会奔溃,在mediarecorder.stop(); 报错为:RuntimeException:stop failed
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setOnInfoListener(null);
mMediaRecorder.setPreviewDisplay(null);
mMediaRecorder.stop();
mMediaRecorder.reset();
} catch (IllegalStateException e)
{
Log.i("Exception", Log.getStackTraceString(e));
} catch (RuntimeException e)
{
Log.i("Exception", Log.getStackTraceString(e));
} catch (Exception e)
{
Log.i("Exception", Log.getStackTraceString(e));
}
mMainHandler.post(new Runnable()
{
@Override
public void run()
{
Toast.makeText(context, "Video saved: " + mNextVideoAbsolutePath, Toast.LENGTH_SHORT).show();
}
});
createCameraPreviewSession();
}
//******************************************************************************************
// private 方法,内部调用
//********************************************************************************************
@Override
public void configureCamera(int width, int height, int cameraNum)
{
try
{
mCameraId = manager.getCameraIdList()[cameraNum];
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
//设置输出选项
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, mVideoSize);
mPreviewSize = new Size(getMeasuredWidth(), getMeasuredHeight());
//如果屏幕旋转需要调整
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
{
setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
} else
{
setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
}
} catch (CameraAccessException e)
{
}
}
private static Size chooseVideoSize(Size[] choices)
{
for (Size size : choices)
{
if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080)
{
return size;
}
}
return choices[choices.length - 1];
}
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
{
return choices[0];
}
}
@Override
public void configureTransform(int width, int height)
{
int rotation = windowManager.getDefaultDisplay().getRotation();
final Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, width, height);
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) height / mPreviewSize.getHeight(),
(float) width / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
if(mMainHandler != null)
{
mMainHandler.post(new Runnable()
{
@Override
public void run()
{
setTransform(matrix);
}
});
}
}
@Override
public void createCameraPreviewSession()
{
try
{
closePreviewSession();
initSurface();
mCameraDevice.createCaptureSession(Arrays.asList(surface), recordSessionStateCallback, mBackgroundHandler);
} catch (CameraAccessException e)
{
e.printStackTrace();
}
}
private void configureMediaRecorder()
{
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mNextVideoAbsolutePath = getVideoFilePath(context);
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = windowManager.getDefaultDisplay().getRotation();
switch (mSensorOrientation)
{
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
break;
case SENSOR_ORIENTATION_INVERSE_DEGREES:
mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
break;
}
try
{
mMediaRecorder.prepare();
} catch (IOException e)
{
e.printStackTrace();
}
}
private String getVideoFilePath(Context context)
{
String picName = SystemClock.currentThreadTimeMillis() + ".mp4";
File file = new File(getSystemPicFile(context), picName);
return file.getAbsolutePath();
}
CameraCaptureSession.StateCallback recordSessionStateCallback = new CameraCaptureSession.StateCallback()
{
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
{
if (null == mCameraDevice)
{
return;
}
mCaptureSession = cameraCaptureSession;
try
{
mPreviewRequestBuilder = CaptureRequestFactory.createPreviewBuilder(mCameraDevice, surface);
CaptureRequestFactory.setPreviewBuilderRecordPreview(mPreviewRequestBuilder);
updatePreview(mPreviewRequestBuilder.build(), null);
if(isRecording)
{
//EventBus给UI发消息,更新按钮。更新完成后,开始录像。
RecordVideoEvent recordVideoEvent = new RecordVideoEvent();
recordVideoEvent.setRecording(true);
EventBus.getDefault().post(recordVideoEvent);
mMediaRecorder.start();
}
} catch (CameraAccessException e)
{
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession)
{
}
};
@Override
public void closeCameraReal()
{
try
{
mCameraOpenCloseLock.acquire();
closePreviewSession();
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();
}
}
//点击事件的处理方法
public void setFlashMode(int flashMode) throws CameraAccessException
{
CaptureRequestFactory.setPreviewBuilderFlash(mPreviewRequestBuilder, flashMode);
updatePreview(mPreviewRequestBuilder.build(), null);
}
}