package com.android.example.cameraappxjava;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
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.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.android.example.cameraappxjava.util.CameraGLRenderer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 调用自定义渲染器的Demo:模拟YUV预览渲染
*/
public class MainActivity2 extends AppCompatActivity {
private static final String TAG = "camera2api";
private static final int REQUEST_CAMERA_PERMISSION = 100;
// 1. 删除 TextureView 相关变量
// private TextureView textureView;
// private boolean isTextureAvailable = false;
// 2. 新增 GLSurfaceView + 自定义渲染器
private GLSurfaceView glSurfaceView;
private CameraGLRenderer cameraGLRenderer; // 之前定义的自定义YUV渲染器
// 3. 新增:预览用 ImageReader(接收 Camera2 输出的 YUV 帧,给渲染器用)
private ImageReader previewImageReader;
// 4. 保留原有拍照用 ImageReader(JPEG格式,不修改)
private ImageReader captureImageReader;
// 5. 保留其他原有变量(相机设备、会话、按钮等)
private Button captureButton;
private CameraDevice cameraDevice;
private CameraCaptureSession cameraCaptureSession;
private CaptureRequest.Builder captureRequestBuilder;
private String cameraId;
private Handler backgroundHandler;
private boolean isSessionClosed;
private HandlerThread backgroundThread;
private CameraManager manager;
private volatile boolean isCapturing = false;
private StreamConfigurationMap map;
private long lastClickTime = 0;
private static final long MIN_CLICK_INTERVAL = 1000;
private File file;
private ContentResolver resolver;
private ContentValues values;
private Uri imageUri;
// ---------------------- 第三步:修改 onCreate(初始化 GLSurfaceView 和渲染器) ----------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate ——————————————————————");
// 1. 初始化 GLSurfaceView(替换原 TextureView)
glSurfaceView = findViewById(R.id.glsurfaceView);
// 2. 初始化拍照按钮(保留原有逻辑)
captureButton = findViewById(R.id.btnCapture);
// 3. 配置 GLSurfaceView + 自定义渲染器(核心)
initGLRenderer();
// 4. 初始化相机参数(保留原有逻辑,但后续需补充预览ImageReader)
initCamera();
// 5. 保留拍照按钮监听(原有逻辑不变)
captureButton.setOnClickListener(v -> {
long currentTime = SystemClock.elapsedRealtime();
if (currentTime - lastClickTime > MIN_CLICK_INTERVAL) {
lastClickTime = currentTime;
takePicture();
} else {
Log.d(TAG, "点击过快,已忽略");
}
});
}
// ---------------------- 新增:初始化 GLSurfaceView 和自定义渲染器 ----------------------
private void initGLRenderer() {
// 1. 设置 OpenGL 版本(必须是 2.0,匹配渲染器着色器)
glSurfaceView.setEGLContextClientVersion(2);
// 2. 创建自定义渲染器实例
cameraGLRenderer = new CameraGLRenderer();
// 3. 绑定渲染器到 GLSurfaceView
glSurfaceView.setRenderer(cameraGLRenderer);
// 4. 按需渲染(有新帧才重绘,节省性能)
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
// ---------------------- 第四步:修改 initCamera(新增预览用 ImageReader) ----------------------
private void initCamera() {
Log.d(TAG, "initCamera: 初始化相机配置");
try {
// 1. 保留原有逻辑:初始化 CameraManager、相机ID、配置Map
manager = (CameraManager) getSystemService(CAMERA_SERVICE);
cameraId = manager.getCameraIdList()[0];
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
Log.e(TAG, "错误: StreamConfigurationMap为空!!");
return;
}
// 2. 新增:初始化预览用 ImageReader(YUV_420_888 格式,给渲染器传数据)
// 2.1 获取相机支持的 YUV 预览尺寸(用原有的尺寸选择逻辑)
Size[] yuvSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
Size previewSize = chooseOptimalSize(yuvSizes, glSurfaceView.getWidth(), glSurfaceView.getHeight());
// 2.2 创建 ImageReader(尺寸=预览尺寸,格式=YUV_420_888,缓冲区=2)
previewImageReader = ImageReader.newInstance(
previewSize.getWidth(),
previewSize.getHeight(),
ImageFormat.YUV_420_888,
2
);
// 2.3 设置 ImageReader 回调(关键:获取 YUV 帧,传给渲染器)
previewImageReader.setOnImageAvailableListener(reader -> {
try (Image image = reader.acquireLatestImage()) {
if (image == null || cameraGLRenderer == null) return;
// 2.5 把 YUV 数据传给渲染器,触发重绘
cameraGLRenderer.setYUVData(image);
glSurfaceView.requestRender(); // 触发渲染器 onDrawFrame
} catch (Exception e) {
Log.e(TAG, "预览帧处理失败: " + e.getMessage());
}
}, backgroundHandler); // 在相机后台线程执行
// 3. 保留原有逻辑:初始化拍照用 ImageReader(JPEG格式)
Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG);
Size captureSize = chooseOptimalSize(jpegSizes,glSurfaceView.getWidth(),glSurfaceView.getHeight());
if (captureImageReader == null || captureImageReader.getWidth() != captureSize.getWidth()) {
if (captureImageReader != null) captureImageReader.close();
captureImageReader = ImageReader.newInstance(
captureSize.getWidth(),
captureSize.getHeight(),
ImageFormat.JPEG,
2
);
}
// 4. 保留原有逻辑:图片保存参数
resolver = getContentResolver();
values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "pic_" + System.currentTimeMillis() + ".jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
} catch (CameraAccessException e) {
Log.e(TAG, "相机访问异常: " + e.getMessage());
} catch (NullPointerException e) {
Log.e(TAG, "NPE: " + e.getMessage());
}
}
// ---------------------- 新增:提取 Image Plane 数据的工具方法 ----------------------
private byte[] extractPlaneData(Image.Plane plane) {
ByteBuffer buffer = plane.getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
return data;
}
// ---------------------- 第五步:修改 openCamera(删除 TextureView 检查) ----------------------
private void openCamera() {
// 1. 删除原 TextureView 相关检查(替换为 ImageReader 检查)
if (previewImageReader == null || backgroundHandler == null) {
Log.w(TAG, "预览ImageReader未就绪,延迟打开相机,1000ms后重试");
backgroundHandler.postDelayed(this::openCamera, 1000);
return;
}
Log.d(TAG, "openCamera: 尝试打开相机");
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "1.打开相机: " + cameraId);
manager.openCamera(cameraId, stateCallback, backgroundHandler);
} else {
Log.w(TAG, "相机权限未授予");
}
} catch (CameraAccessException e) {
Log.e(TAG, "打开相机失败: " + e.getMessage());
} catch (SecurityException e) {
Log.e(TAG, "安全异常: " + e.getMessage());
}
}
// ---------------------- 第六步:修改 createCameraPreviewSession(替换预览 Surface) ----------------------
private void createCameraPreviewSession() {
if (cameraDevice == null || previewImageReader == null) {
Log.e(TAG, "创建预览会话失败: 相机或预览ImageReader不可用");
return;
}
try {
// 1. 新增:获取预览 ImageReader 的 Surface(Camera2 输出目标)
Surface previewSurface = previewImageReader.getSurface();
// 2. 保留:获取拍照 ImageReader 的 Surface
Surface captureSurface = captureImageReader.getSurface();
// 3. 配置双输出 Surface(预览 + 拍照,替换原 TextureView 的 Surface)
List<Surface> outputSurfaces = new ArrayList<>(2);
outputSurfaces.add(previewSurface); // 预览:ImageReader 的 Surface(给渲染器)
outputSurfaces.add(captureSurface); // 拍照:原有 ImageReader 的 Surface
// 4. 保留原有逻辑:创建会话 + 配置预览请求
cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
Log.i(TAG, "2.2 预览会话配置成功");
cameraCaptureSession = session;
try {
// 配置预览请求(目标是预览 Surface)
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(previewSurface); // 替换为 ImageReader 的 Surface
// 保留原有自动对焦/闪光灯配置
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
Log.i(TAG, "3.开始下发预览请求");
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "设置预览请求失败: " + e.getMessage());
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "预览会话配置失败");
Toast.makeText(MainActivity2.this, "配置失败", Toast.LENGTH_SHORT).show();
}
}, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "创建预览会话异常: " + e.getMessage());
}
}
// ---------------------- 第七步:修改生命周期方法(添加 GLSurfaceView 管理) ----------------------
@Override
protected void onResume() {
Log.d(TAG, "onResume —————————————————————— ");
super.onResume();
// 1. 保留原有权限检查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "没有相机权限——>开始请求相机权限");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
return;
}
// 2. 保留原有后台线程启动
startBackgroundThread();
// 3. 新增:恢复 GLSurfaceView(必须调用,否则渲染暂停)
glSurfaceView.onResume();
// 4. 打开相机(替换原 TextureView 检查)
openCamera();
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause ——————————————————————");
// 1. 新增:暂停 GLSurfaceView(必须调用,保存 OpenGL 上下文)
glSurfaceView.onPause();
// 2. 保留原有预览暂停逻辑
if (!isCapturing && cameraCaptureSession != null) {
try {
cameraCaptureSession.stopRepeating();
Log.d(TAG, "onPause: 暂停预览重复请求(核心资源未释放)");
} catch (CameraAccessException e) {
Log.e(TAG, "onPause: 停止预览失败", e);
}
}
// 3. 保留原有拍照中延迟处理逻辑
if (isCapturing) {
Log.w(TAG, "onPause: 拍照中,暂不处理预览暂停");
new Handler().postDelayed(() -> {
if (!isCapturing && cameraCaptureSession != null) {
try {
cameraCaptureSession.stopRepeating();
Log.d(TAG, "onPause: 拍照完成后,暂停预览");
} catch (CameraAccessException e) {
Log.e(TAG, "onPause: 延迟停止预览失败", e);
}
}
}, 1000);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: Activity 彻底销毁,释放所有资源");
// 1. 新增:释放预览 ImageReader 和渲染器资源
if (previewImageReader != null) {
previewImageReader.close();
}
if (cameraGLRenderer != null) {
cameraGLRenderer.release();
}
// 2. 保留原有资源释放逻辑(相机、拍照ImageReader、线程等)
if (cameraCaptureSession != null) {
cameraCaptureSession.close();
cameraCaptureSession = null;
}
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
if (captureImageReader != null) {
captureImageReader.close();
captureImageReader = null;
}
stopBackgroundThread();
// 3. 置空新增的引用
glSurfaceView = null;
cameraGLRenderer = null;
previewImageReader = null;
// 4. 保留原有置空逻辑
captureButton = null;
manager = null;
resolver = null;
values = null;
imageUri = null;
backgroundHandler = null;
backgroundThread = null;
Log.d(TAG, "onDestroy: 所有资源释放完成");
}
private boolean checkTakePicture() {
if (cameraDevice == null) {
Log.w(TAG, "拍照失败: 相机未初始化");
return false;
}
// 1. 检查会话有效性
if (cameraCaptureSession == null) {
Log.e(TAG, "拍照错误: CameraCaptureSession为空");
return false;
}
// 2. 检查后台Handler
if (backgroundHandler == null) {
Log.e(TAG, "拍照错误: backgroundHandler未初始化");
startBackgroundThread(); // 初始化方法见下方
return false;
}
if (isSessionClosed) {
Log.e(TAG, "当前会话已关闭");
}
return true;
}
// ---------------------- 第八步:修改 takePicture(替换拍照用 ImageReader) ----------------------
private void takePicture() {
Log.i(TAG, "4.开始拍照流程——————————");
try {
// 1. 保留原有检查逻辑
boolean checkFlag = checkTakePicture();
if (!checkFlag) {
Log.i(TAG, "拍照流程————检查未通过!退出拍照!");
return;
}
// 2. 替换:拍照请求目标为 captureImageReader(原有 JPEG 格式)
CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(captureImageReader.getSurface()); // 用拍照专用 ImageReader
// 3. 保留原有拍照参数配置
captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotation);
// 4. 保留原有拍照 ImageReader 回调(保存 JPEG 图片)
captureImageReader.setOnImageAvailableListener(reader -> {
Log.d(TAG, "拍照图像数据可用");
try (Image image = reader.acquireLatestImage()) {
if (image != null) {
// 保留原有文件创建和保存逻辑
file = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"pic_" + System.currentTimeMillis() + ".jpg"
);
// 提取 JPEG 数据(原有逻辑)
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
// 保存图片(原有逻辑)
saveImage(bytes, file);
// 保留原有广播和提示
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(file));
sendBroadcast(mediaScanIntent);
runOnUiThread(() ->
Toast.makeText(MainActivity2.this, "保存至: " + file, Toast.LENGTH_SHORT).show()
);
}
} catch (Exception e) {
Log.e(TAG, "保存拍照图像错误: " + e.getMessage());
} finally {
isCapturing = false;
// 恢复预览(重新下发预览请求)
if (cameraCaptureSession != null && captureRequestBuilder != null) {
try {
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "恢复预览失败: " + e.getMessage());
}
}
}
}, backgroundHandler);
// 5. 保留原有拍照执行逻辑
Log.d(TAG, "停止预览");
cameraCaptureSession.stopRepeating();
Log.d(TAG, "4.下发拍照");
isCapturing = true;
cameraCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
Log.e(TAG, "拍照失败: " + failure.getReason());
isCapturing = false;
}
}, backgroundHandler);
} catch (CameraAccessException | IllegalStateException | SecurityException e) {
Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e);
isCapturing = false;
}
}
// ---------------------- 保留原有未修改的方法 ----------------------
// (包括:chooseOptimalSize、CompareSizesByArea、stateCallback、saveImage、onRequestPermissionsResult、startBackgroundThread、stopBackgroundThread、closeCamera、checkTakePicture)
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
}
}
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.i(TAG, "相机已打开");
cameraDevice = camera;
Log.i(TAG, "2.1 开始配置预览流");
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.w(TAG, "相机断开连接");
cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "相机错误: " + error);
cameraDevice.close();
cameraDevice = null;
}
};
private void saveImage(byte[] bytes, File file) {
Log.d(TAG, "保存图像: " + file.getAbsolutePath());
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (imageUri != null) {
try (FileOutputStream output = new FileOutputStream(file)) {
output.write(bytes);
Log.i(TAG, "图像保存成功, 大小: " + bytes.length + " bytes");
} catch (IOException e) {
Log.e(TAG, "保存文件失败: " + e.getMessage());
}
}
}
//触发时机:用户点击授权后调用
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "权限请求结果: " + requestCode);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
Log.w(TAG, "用户拒绝相机权限");
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show();
finish();
} else {
Log.i(TAG, "用户授予相机权限");
startBackgroundThread();
openCamera();
}
}
}
private void stopBackgroundThread() {
if (backgroundThread != null) {
Log.d(TAG, "停止后台线程");
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException e) {
Log.e(TAG, "停止线程失败: " + e.getMessage());
}
}
}
private void startBackgroundThread() {
if (backgroundThread == null) {
backgroundThread = new HandlerThread("CameraBackground");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
Log.d(TAG, "后台线程启动");
}
}
private void closeCamera() {
Log.d(TAG, "关闭相机资源");
if (isCapturing) {
Log.w(TAG, "正在拍照中,等待完成或取消...");
// 可以尝试等待一段时间或取消请求
try {
cameraCaptureSession.abortCaptures(); // 取消所有进行中的捕获
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
if (cameraCaptureSession != null) {
cameraCaptureSession.close();
cameraCaptureSession = null;
}
isSessionClosed = true;
}
private Size chooseOptimalSize(Size[] choices, int width, int height) {
List<Size> bigEnough = new ArrayList<>();
for (Size option : choices) {
float ratio = (float) option.getWidth() / option.getHeight();
float viewRatio = (float) width / height;
if (Math.abs(ratio - viewRatio) <= 0.1 &&
option.getWidth() <= width &&
option.getHeight() <= height) {
bigEnough.add(option);
}
}
if (!bigEnough.isEmpty()) {
return Collections.max(bigEnough, new CompareSizesByArea());
}
Log.w(TAG, "未找到完美匹配尺寸,使用默认");
return choices[0];
}
}我发现并没有调用CameraGLRenderer中的onDrawFrame方法,这是为什么
最新发布