package com.whyc.widget;
|
|
import android.annotation.SuppressLint;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.graphics.ImageFormat;
|
import android.graphics.SurfaceTexture;
|
import android.hardware.camera2.CameraAccessException;
|
import android.hardware.camera2.CameraCaptureSession;
|
import android.hardware.camera2.CameraDevice;
|
import android.hardware.camera2.CameraManager;
|
import android.hardware.camera2.CameraMetadata;
|
import android.hardware.camera2.CaptureRequest;
|
import android.media.Image;
|
import android.media.ImageReader;
|
import android.media.MediaCodec;
|
import android.media.MediaCodecInfo;
|
import android.media.MediaFormat;
|
import android.media.MediaMuxer;
|
import android.media.MediaScannerConnection;
|
import android.net.Uri;
|
import android.os.Environment;
|
import android.os.Handler;
|
import android.os.HandlerThread;
|
import android.support.annotation.NonNull;
|
import android.util.AttributeSet;
|
import android.view.Surface;
|
import android.view.TextureView;
|
import android.view.View;
|
import android.widget.LinearLayout;
|
import android.widget.TextView;
|
import android.widget.Toast;
|
|
import java.io.File;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.nio.ByteBuffer;
|
import java.util.Arrays;
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import static android.os.Environment.DIRECTORY_DOCUMENTS;
|
|
/**这个采用ImageReader视频帧来获取视频和处理图片*/
|
public class Camera2TextureView3 extends TextureView {
|
|
private Context mContext;
|
private CameraDevice mCameraDevice;
|
private CaptureRequest.Builder captureBuilder = null;
|
private CameraCaptureSession mCaptureSession;
|
private Surface previewSurface;
|
private ImageReader mImageReader;
|
private Surface surfaceForStream;
|
|
CaptureRequest.Builder recordRequest;
|
|
private Handler mHandler;
|
private HandlerThread mThreadHandler;
|
ScheduledExecutorService executorService;
|
|
//视频和图片的文件夹路径
|
File fileDir = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() + "/yc_test/");
|
|
|
//主页面的视图
|
private LinearLayout llUpText;
|
private TextView tvDevice;
|
|
public Camera2TextureView3(Context context) {
|
super(context,null);
|
}
|
|
public Camera2TextureView3(Context context, AttributeSet attrs) throws IOException {
|
super(context, attrs);
|
mContext = context;
|
mThreadHandler = new HandlerThread("camera2");
|
mThreadHandler.start();
|
mHandler = new Handler(mThreadHandler.getLooper());
|
}
|
|
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) {
|
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 cameraId = cm.getCameraIdList()[0];
|
cm.openCamera(cameraId,mDeviceStateCallback,null);
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
|
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
|
@Override
|
public void onOpened(CameraDevice cameraDevice) {
|
mCameraDevice = cameraDevice;
|
SurfaceTexture surfaceTexture = getSurfaceTexture();
|
surfaceTexture.setDefaultBufferSize(1920, 1080);
|
previewSurface = new Surface(surfaceTexture);
|
|
try {
|
createPreviewSession();
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
|
@Override
|
public void onDisconnected(@NonNull CameraDevice camera) {
|
|
}
|
|
@Override
|
public void onError(@NonNull CameraDevice camera, int error) {
|
|
}
|
};
|
|
private void createPreviewSession() {
|
try {
|
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() {
|
@Override
|
public void onConfigured(@NonNull CameraCaptureSession session) {
|
mCaptureSession = session;
|
startPreview(mCameraDevice);
|
}
|
|
@Override
|
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
|
|
}
|
}, null);
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
private void startPreview(CameraDevice cameraDevice) {
|
//启动预览
|
try {
|
captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
captureBuilder.addTarget(previewSurface);
|
|
/*设置预览的界面*/
|
//设置自动对焦模式
|
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
CaptureRequest.CONTROL_AF_MODE_AUTO);
|
// 设置自动曝光模式
|
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
// 开始对焦
|
captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
|
CameraMetadata.CONTROL_AF_TRIGGER_START);
|
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, 90);
|
|
mCaptureSession.setRepeatingRequest(captureBuilder.build(), null, mHandler);
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
}
|
|
private static final String MIME_TYPE = "video/avc"; // H.264 AVC
|
private static final int FRAME_RATE = 30;
|
private static final int IFRAME_INTERVAL = 5; // seconds
|
|
private MediaCodec encoder;
|
private MediaMuxer muxer;
|
private int encoderColorFormat;
|
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
//视频相关初始化 recorderPath = recorderFile.getAbsolutePath()+File.separator + System.currentTimeMillis() + ".mp4";
|
|
private void initVideo(String outputPath) throws IOException {
|
encoder = MediaCodec.createEncoderByType(MIME_TYPE);
|
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, 1920, 1080);
|
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 10000000);
|
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
|
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
|
encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
encoder.start();
|
|
encoderColorFormat = encoder.getInputFormat().getInteger(MediaFormat.KEY_COLOR_FORMAT);
|
|
muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, 1920, 1080);
|
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, encoderColorFormat);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, 10000000);
|
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
|
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
|
int track = muxer.addTrack(format);
|
muxer.start();
|
}
|
private void encodeFrame(Image image) {
|
ByteBuffer[] inputBuffers = encoder.getInputBuffers();
|
int index = encoder.dequeueInputBuffer(-1);
|
if (index >= 0) {
|
ByteBuffer inputBuffer = inputBuffers[index];
|
if (inputBuffer == null) {
|
image.close();
|
return;
|
}
|
inputBuffer.clear();
|
// 计算需要复制的数据大小
|
|
final Image.Plane[] planes = image.getPlanes();
|
fillRect(inputBuffer, planes[0].getBuffer()); // Y plane
|
// fillRect(inputBuffer, planes[1].getBuffer()); // U plane
|
// fillRect(inputBuffer, planes[2].getBuffer()); // V plane
|
// image.copyPlanes(inputBuffer, image.getPlanes());
|
long presentationTimeUs = System.nanoTime() / 1000;
|
encoder.queueInputBuffer(index, 0, image.getWidth() * image.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8, presentationTimeUs, 0);
|
image.close();
|
|
int encoderStatus = encoder.dequeueOutputBuffer(bufferInfo, 10000);
|
if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
ByteBuffer encodedData = encoder.getOutputBuffer(encoderStatus);
|
if (encodedData != null) {
|
encodedData.position(bufferInfo.offset);
|
encodedData.limit(bufferInfo.offset + bufferInfo.size);
|
muxer.writeSampleData(0, encodedData, bufferInfo);
|
encoder.releaseOutputBuffer(encoderStatus, false);
|
}
|
}
|
}
|
}
|
|
private void fillRect(ByteBuffer dst, ByteBuffer src) {
|
// This is a simple method to copy one ByteBuffer into another.
|
// Depending on the format and alignment requirements of your codec,
|
// you may need to do more complex processing here.
|
byte[] data = new byte[src.remaining()];
|
src.get(data);
|
dst.put(data);
|
}
|
|
public void createRecorderSession() throws IOException {
|
// mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
|
mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.YUV_420_888, 1);
|
surfaceForStream = mImageReader.getSurface();
|
String recorderPath = fileDir.getAbsolutePath()+File.separator + System.currentTimeMillis() + ".mp4";
|
initVideo(recorderPath);
|
|
mImageReader.setOnImageAvailableListener(mImageReaderListener, null);
|
|
try {
|
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,surfaceForStream), new CameraCaptureSession.StateCallback() {
|
@Override
|
public void onConfigured(@NonNull CameraCaptureSession session) {
|
mCaptureSession = session;
|
try {
|
//camera2 视频录制模式
|
recordRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
|
recordRequest.addTarget(previewSurface);
|
recordRequest.addTarget(mImageReader.getSurface());
|
|
mCaptureSession.setRepeatingRequest(recordRequest.build(), null, mHandler); // 开始录制视频
|
|
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
}
|
|
@Override
|
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
|
|
}
|
}, null);
|
// CaptureRequest.Builder recordRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
|
|
|
} catch (CameraAccessException e) {
|
e.printStackTrace();
|
}
|
}
|
|
private ImageReader.OnImageAvailableListener mImageReaderListener = reader -> {
|
/*long now = System.currentTimeMillis();
|
SharedPreferences camera2Time = mContext.getSharedPreferences("camera2Time", Context.MODE_PRIVATE);
|
long lastTime = camera2Time.getLong("time", 0);
|
if (lastTime == 0 || now-lastTime > 1000) {
|
//这里只是演示test
|
llUpText.setVisibility(View.VISIBLE);
|
tvDevice.setText("这是测试的动态赋值");
|
camera2Time.edit().putLong("time", now).apply();
|
|
// 获取到拍照的图像数据
|
Image image = reader.acquireNextImage();
|
|
// 获取图片的字节数组
|
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
byte[] data = new byte[buffer.remaining()];
|
buffer.get(data);
|
// 释放图像资源
|
image.close();
|
|
// 保存图片到相册
|
saveImageToGallery(data);
|
}else{
|
// 获取到拍照的图像数据
|
Image image = reader.acquireNextImage();
|
image.close();
|
}*/
|
// 获取到拍照的图像数据
|
Image image = reader.acquireNextImage();
|
encodeFrame(image);
|
//这里只是演示test
|
llUpText.setVisibility(View.VISIBLE);
|
tvDevice.setText("这是测试的动态赋值");
|
/* // 获取图片的字节数组
|
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
byte[] data = new byte[buffer.remaining()];
|
buffer.get(data);
|
// 保存图片到相册
|
saveImageToGallery(data);
|
// 释放图像资源
|
image.close();*/
|
};
|
|
private void saveImageToGallery(byte[] data) {
|
// 定义图片的保存路径和文件名
|
String fileName = "IMG_" + System.currentTimeMillis() + ".jpg";
|
String filePath = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() + "/yc_test"+ File.separator + fileName;
|
|
// 创建文件输出流
|
try {
|
FileOutputStream fos = new FileOutputStream(filePath);
|
fos.write(data);
|
fos.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() {
|
//这里只是演示test
|
llUpText.setVisibility(View.GONE);
|
//停止画面图片监听
|
mImageReader.close();
|
// 停止录制视频
|
try {
|
executorService.shutdownNow();
|
// createPreviewSession();
|
} catch (IllegalStateException e) {
|
e.printStackTrace();
|
} finally {
|
// mMediaRecorder.reset();
|
// mMediaRecorder.release();
|
// mMediaRecorder = null;
|
}
|
|
// 关闭相机预览会话
|
// if (captureSession != null) {
|
// captureSession.close();
|
// captureSession = null;
|
// }
|
|
//图库更新
|
// addToGallery(recorderPath);
|
}
|
|
private void addToGallery(String videoFilePath) {
|
// 发送广播通知图库更新
|
MediaScannerConnection.scanFile(mContext, new String[]{videoFilePath}, null,
|
(path, uri) -> {
|
// 添加到相册成功的回调
|
Toast.makeText(mContext, "视频已保存至相册", Toast.LENGTH_SHORT).show();
|
});
|
}
|
}
|