package com.whyc.widget;
|
|
import android.annotation.SuppressLint;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.graphics.Bitmap;
|
import android.graphics.ImageFormat;
|
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.CaptureRequest;
|
import android.media.Image;
|
import android.media.ImageReader;
|
import android.media.MediaRecorder;
|
import android.media.MediaScannerConnection;
|
import android.net.Uri;
|
import android.os.Environment;
|
import android.os.Handler;
|
import android.os.HandlerThread;
|
import android.os.Looper;
|
import android.support.annotation.NonNull;
|
import android.util.AttributeSet;
|
import android.util.Log;
|
import android.util.Size;
|
import android.view.Surface;
|
import android.view.TextureView;
|
import android.widget.LinearLayout;
|
import android.widget.TextView;
|
import android.widget.Toast;
|
|
import com.whyc.util.BitmapUtil;
|
|
import java.io.File;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.nio.ByteBuffer;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.concurrent.Executors;
|
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.TimeUnit;
|
|
import static android.content.ContentValues.TAG;
|
import static android.os.Environment.DIRECTORY_DOCUMENTS;
|
|
/**
|
* 这个采用TEMPLATE_RECORD和TEMPLATE_VIDEO_SNAPSHOT模板,TEMPLATE_RECORD模板是录制视频
|
* 改为:
|
* 副摄 ->录像
|
* 主摄-> 拍照
|
* */
|
public class Camera2TextureView2 extends TextureView {
|
|
private Context mContext;
|
//主摄,后摄
|
private CameraDevice mainCameraDevice;
|
//副摄,前摄
|
private CameraDevice subCameraDevice;
|
|
private CaptureRequest.Builder captureCaptureBuilder = null;
|
private CaptureRequest.Builder videoCaptureBuilder = null;
|
private CaptureRequest.Builder previewCaptureBuilder = null;
|
private CameraCaptureSession mainCaptureSession;
|
private CameraCaptureSession subCaptureSession;
|
private MediaRecorder mMediaRecorder;
|
private Surface previewSurface;
|
private ImageReader mImageReader;
|
private Surface surfaceForStream;
|
|
/**线程处理器 分别处理 录像,预览,拍照*/
|
private Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
private HandlerThread videoThread;
|
private HandlerThread previewThread;
|
private HandlerThread captureThread;
|
private HandlerThread imageThread;
|
|
private Handler videoThreadHandler;
|
private Handler previewThreadHandler;
|
private Handler captureThreadHandler;
|
private Handler imageThreadHandler;
|
|
|
ScheduledExecutorService scheduledExecutorService;
|
|
//主页面的视图
|
private LinearLayout llUpText;
|
private TextView tvDevice;
|
|
private int wight = 3840;
|
private int height = 2160;
|
// private int wight = 1920;
|
// private int height = 1080;
|
|
private int videoWidth = 1920;
|
private int videoHeight = 1080;
|
Size outputSizes = new Size(wight,height);
|
|
|
public Camera2TextureView2(Context context) {
|
super(context,null);
|
}
|
|
public Camera2TextureView2(Context context, AttributeSet attrs) {
|
super(context, attrs);
|
// executorService = Executors.newScheduledThreadPool(4);
|
/*String url = "http://192.168.10.79:8919/fg/server/timestamp";
|
executorService.execute(()-> {
|
HttpReqData httpReqData = new HttpReqData(url);
|
HttpRespData data = HttpRequestUtil.getData(httpReqData);
|
System.out.println("-------------------------------------------heirong wei---"+data.content);
|
});*/
|
|
mContext = context;
|
}
|
|
public void init(LinearLayout llUpText, TextView tvDevice) {
|
this.llUpText = llUpText;
|
this.tvDevice = tvDevice;
|
setSurfaceTextureListener(mSurfaceTextureListener);
|
}
|
|
private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {
|
@Override
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
//预览surface
|
SurfaceTexture surfaceTexture = getSurfaceTexture();
|
surfaceTexture.setDefaultBufferSize(outputSizes.getWidth(), outputSizes.getHeight());
|
previewSurface = new Surface(surfaceTexture);
|
//页面加载后初始化线程处理器
|
initPreviewThreadHandler();
|
initCamera2();
|
}
|
@Override
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
|
}
|
@Override
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
return false;
|
}
|
@Override
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
|
}
|
};
|
|
@SuppressLint("MissingPermission")
|
private void initCamera2() {
|
//获取权限
|
// ActivityCompat.requestPermissions(Camera2TextureViewActivity.this, permissionArray, 1);
|
CameraManager cm = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
|
try {
|
//获取前摄像头
|
String[] cameraIds = cm.getCameraIdList();
|
for (int i = 0; i < cameraIds.length; i++) {
|
String cameraId = cameraIds[i];
|
CameraCharacteristics cameraCharacteristics = cm.getCameraCharacteristics(cameraId);
|
if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT){
|
cm.openCamera(cameraId,subDeviceStateCallback,null);
|
}else if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK){
|
cm.openCamera(cameraId,mainDeviceStateCallback,null);
|
}
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
|
private CameraDevice.StateCallback subDeviceStateCallback = new CameraDevice.StateCallback() {
|
@Override
|
public void onOpened(CameraDevice cameraDevice) {
|
subCameraDevice = cameraDevice;
|
}
|
|
@Override
|
public void onDisconnected(@NonNull CameraDevice camera) {
|
|
}
|
|
@Override
|
public void onError(@NonNull CameraDevice camera, int error) {
|
|
}
|
};
|
|
private CameraDevice.StateCallback mainDeviceStateCallback = new CameraDevice.StateCallback() {
|
@Override
|
public void onOpened(CameraDevice cameraDevice) {
|
mainCameraDevice = cameraDevice;
|
|
//拍照的接收器
|
mImageReader = ImageReader.newInstance(outputSizes.getWidth(), outputSizes.getHeight(), ImageFormat.YUV_420_888, 2);
|
// mImageReader = ImageReader.newInstance(outputSizes.getWidth(), outputSizes.getHeight(), ImageFormat.JPEG, 4);
|
//创建主摄像头会话并预览
|
createPreviewSession();
|
}
|
|
@Override
|
public void onDisconnected(@NonNull CameraDevice camera) {
|
|
}
|
|
@Override
|
public void onError(@NonNull CameraDevice camera, int error) {
|
|
}
|
};
|
|
private void createPreviewSession() {
|
try {
|
|
mainCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
|
@Override
|
public void onConfigured(@NonNull CameraCaptureSession session) {
|
mainCaptureSession = session;
|
try {
|
previewCaptureBuilder = mainCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
previewCaptureBuilder.addTarget(previewSurface);
|
startPreview();
|
}
|
|
@Override
|
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
|
|
}
|
}, null);
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
private void startPreview() {
|
//启动预览
|
try {
|
mainCaptureSession.setRepeatingRequest(previewCaptureBuilder.build(), null, previewThreadHandler);
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
}
|
|
private File recorderFile;
|
private String recorderPath;
|
private boolean isRecording = false;
|
|
private void initRecorder() {
|
// recorderFile = new File(Camera2Activity.this.getExternalFilesDir(null).getAbsolutePath() + "/video/");
|
// recorderFile = new File(mContext.getExternalFilesDir(null).getAbsolutePath() + "/video/");
|
// recorderFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
recorderFile = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() + "/yc_test/");
|
if (!recorderFile.exists()) {
|
recorderFile.mkdirs();
|
}
|
recorderPath = recorderFile.getAbsolutePath()+File.separator + System.currentTimeMillis() + ".mp4";
|
Log.e(TAG, "视频路径:" + recorderPath);
|
mMediaRecorder = new MediaRecorder();
|
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
|
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
mMediaRecorder.setOutputFile(recorderPath);
|
mMediaRecorder.setVideoEncodingBitRate(10000000);
|
mMediaRecorder.setVideoFrameRate(20);
|
mMediaRecorder.setVideoSize(videoWidth, videoHeight);
|
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
|
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
}
|
|
public void createRecorderSession(){
|
if(mMediaRecorder!=null) {
|
mMediaRecorder.reset();
|
}
|
initRecorder();
|
//线程开启
|
initThreadHandlerForRecording();
|
mImageReader.setOnImageAvailableListener(mImageReaderListener, imageThreadHandler);
|
try{
|
mMediaRecorder.prepare();
|
mMediaRecorder.start();
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
try {
|
subCameraDevice.createCaptureSession(Arrays.asList(mMediaRecorder.getSurface()), new CameraCaptureSession.StateCallback() {
|
@Override
|
public void onConfigured(@NonNull CameraCaptureSession session) {
|
subCaptureSession = session;
|
try {
|
//camera2 视频录制模式
|
videoCaptureBuilder = subCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
|
videoCaptureBuilder.addTarget(mMediaRecorder.getSurface());
|
|
// 开始录制视频
|
subCaptureSession.setRepeatingRequest(videoCaptureBuilder.build(), null, videoThreadHandler);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
}
|
|
@Override
|
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
|
|
}
|
}, null);
|
//camera2 3秒钟1次的定时任务
|
//对焦并拍照
|
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
|
@Override
|
public void run() {
|
takePhoto();
|
}
|
}, 1, 2, TimeUnit.SECONDS);
|
// focus();
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
}
|
|
private void takePhoto() {
|
try {
|
// 对焦完成,开始拍照
|
/*==========摄像头的模式设置==========*/
|
// 设置自动白平衡
|
// captureCaptureBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);
|
// 设置对焦模式
|
// captureCaptureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
try {
|
if(captureCaptureBuilder == null) {
|
captureCaptureBuilder = mainCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
|
captureCaptureBuilder.addTarget(mImageReader.getSurface());
|
captureCaptureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
}
|
mainCaptureSession.capture(captureCaptureBuilder.build(),null, captureThreadHandler);
|
// Log.d("Camera2", "拍照成功:");
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
|
private ImageReader.OnImageAvailableListener mImageReaderListener = reader -> {
|
Image image = null;
|
try {
|
// Log.d("Camera2", "获取到图像数据:");
|
image = reader.acquireNextImage();
|
// 获取到拍照的图像数据
|
//这里只是演示test
|
// llUpText.setVisibility(View.VISIBLE);
|
// tvDevice.setText("这是测试的动态赋值");
|
// 保存图片到相册-这种方式是JEPG格式
|
// saveImageToGallery(image);
|
//YUV420
|
|
// int I420size = image.getWidth()*image.getHeight()*3/2;
|
// byte[] nv21 = new byte[I420size];
|
// YUVUtil.YUVToNV21_NV12(image,nv21,outputSizes.getWidth(),outputSizes.getHeight(),"NV21");
|
List<byte[]> planetBytes = getPlanetBytes(image);
|
byte[] nv21 = byteMergerAll(planetBytes.get(0), planetBytes.get(1));
|
Bitmap bitmap = BitmapUtil.nv21ToBitmap(nv21, outputSizes.getWidth(), outputSizes.getHeight());
|
BitmapUtil.saveBitmapToFile(bitmap);
|
}catch (Exception e){
|
e.printStackTrace();
|
}finally {
|
if(image!=null) {
|
image.close();
|
}
|
}
|
};
|
|
private List<byte[]> getPlanetBytes(Image image) {
|
Image.Plane[] planes = image.getPlanes();
|
int remaining0 = planes[0].getBuffer().remaining();
|
// int remaining1 = planes[1].getBuffer().remaining();
|
int remaining2 = planes[2].getBuffer().remaining();
|
//分别准备三个数组接收YUV分量。
|
byte[] yRawSrcBytes = new byte[remaining0];
|
// byte[] uRawSrcBytes = new byte[remaining1];
|
byte[] vRawSrcBytes = new byte[remaining2];
|
planes[0].getBuffer().get(yRawSrcBytes);
|
// planes[1].getBuffer().get(uRawSrcBytes);
|
planes[2].getBuffer().get(vRawSrcBytes);
|
|
List<byte[]> yuvBytes = new ArrayList<>();
|
yuvBytes.add(yRawSrcBytes);
|
yuvBytes.add(vRawSrcBytes);
|
return yuvBytes;
|
}
|
|
private static byte[] byteMergerAll(byte[]... values) {
|
int length_byte = 0;
|
for (int i = 0; i < values.length; i++) {
|
length_byte += values[i].length;
|
}
|
byte[] all_byte = new byte[length_byte];
|
int countLength = 0;
|
for (int i = 0; i < values.length; i++) {
|
byte[] b = values[i];
|
System.arraycopy(b, 0, all_byte, countLength, b.length);
|
countLength += b.length;
|
}
|
return all_byte;
|
}
|
|
private void saveImageToGallery(Image image) {
|
// 定义图片的保存路径和文件名
|
String fileName = "IMG_" + System.currentTimeMillis() + ".jpg";
|
String filePath = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() + "/yc_test"+ File.separator + fileName;
|
|
// 获取图片的字节数组
|
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
byte[] data = new byte[buffer.remaining()];
|
buffer.get(data);
|
// 创建文件输出流
|
try {
|
FileOutputStream fos = new FileOutputStream(filePath);
|
fos.write(data);
|
fos.close();
|
image.close();
|
// 通知图库更新
|
MediaScannerConnection.scanFile(mContext, new String[]{filePath}, null, null);
|
|
// 在某些设备上,可能需要发送广播通知才能使图片立即出现在相册中
|
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(filePath))));
|
|
// 显示保存成功的提示
|
// Toast.makeText(this, "图片保存成功", Toast.LENGTH_SHORT).show();
|
} catch (IOException e) {
|
e.printStackTrace();
|
// 显示保存失败的提示
|
// Toast.makeText(this, "图片保存失败", Toast.LENGTH_SHORT).show();
|
}
|
}
|
|
public void stopRecording() {
|
try {
|
// 停止录制视频,关闭录像请求
|
mMediaRecorder.stop();
|
//关闭录制视频会话
|
subCaptureSession.stopRepeating();
|
//关闭拍照会话
|
scheduledExecutorService.shutdownNow();
|
//停止画面图片监听
|
mImageReader.setOnImageAvailableListener(null, null);
|
//终止线程
|
stopThreadHandlerForRecording();
|
|
//这里只是演示test
|
// llUpText.setVisibility(View.GONE);
|
} catch (Exception e) {
|
e.printStackTrace();
|
} finally {
|
mMediaRecorder.reset();
|
// mMediaRecorder.release();
|
// mMediaRecorder = null;
|
//图库更新
|
addToGallery(recorderPath);
|
isRecording = false;
|
}
|
|
}
|
|
private void addToGallery(String videoFilePath) {
|
// 发送广播通知图库更新
|
MediaScannerConnection.scanFile(mContext, new String[]{videoFilePath}, null,
|
(path, uri) -> {
|
// 添加到相册成功的回调
|
Toast.makeText(mContext, "视频已保存至相册", Toast.LENGTH_SHORT).show();
|
});
|
}
|
|
private void initPreviewThreadHandler(){
|
//预览的处理线程
|
previewThread = new HandlerThread("camera2Preview");
|
previewThread.start();
|
previewThreadHandler = new Handler(previewThread.getLooper());
|
|
//图像监测的处理线程
|
imageThread = new HandlerThread("camera2Image");
|
imageThread.start();
|
imageThreadHandler = new Handler(imageThread.getLooper());
|
}
|
|
/**
|
* 录像 一个线程处理器
|
* 拍照 一个线程处理器
|
* 图像监测 一个线程处理器
|
*/
|
private void initThreadHandlerForRecording() {
|
//录像的处理线程
|
videoThread = new HandlerThread("Camera2Video");
|
videoThread.start();
|
videoThreadHandler = new Handler(videoThread.getLooper());
|
|
//拍照的处理线程
|
captureThread = new HandlerThread("camera2Capture");
|
captureThread.start();
|
captureThreadHandler = new Handler(captureThread.getLooper());
|
|
//图像监测的处理线程
|
if(imageThreadHandler == null) {
|
imageThread = new HandlerThread("camera2Image");
|
imageThread.start();
|
imageThreadHandler = new Handler(imageThread.getLooper());
|
}
|
}
|
|
private void stopThreadHandlerForRecording() {
|
try {
|
videoThread.quitSafely();
|
videoThread.join();
|
videoThread = null;
|
videoThreadHandler = null;
|
|
captureThread.quitSafely();
|
captureThread.join();
|
captureThread = null;
|
captureThreadHandler = null;
|
|
imageThread.quitSafely();
|
imageThread.join();
|
imageThread = null;
|
imageThreadHandler = null;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
}
|
}
|
}
|