Android 自定义相机开发(三) —— EGL介绍

本文详细介绍了EGL在Android自定义相机开发中的作用,解释了EGL与OpenGl ES的关系,阐述了EGL的基本使用步骤,包括创建Display、Surface、Context,以及如何在EGL环境下进行渲染工作。通过分析GlSurfaceView源码,揭示了EGL在实际应用中的配置过程。

胡说八道

如果要使用OpenGl来自定义相机,这个还是要了解下的。可能大多数开发者使用过OpengGL但是不知道EGL是什么?EGL的作用是什么?这其实一点都不奇怪,因为Android中的GlSurfaceView已经将EGL环境都给配置好了,你一直在使用,只是不知道他的存在罢了。很多人可能在使用OpenGl ES渲染数据的时候都带着一个疑问,渲染的数据到底到哪里去了?没看到画布,Android中的自定义view不都是有画布的吗?这篇文章就是为了解决这一系列问题而服务的。当然,如果你对自定义相机,音视频编码开发感兴趣,可以了解下我的Chat点这里

正儿八经

通过本文你可以了解到如下内容:1. 什么是EGL?. 2. EGL和OpenGl ES的关系 3. 使用EGL绘图的基本步骤。4. GlSurfaceView源码中分析EGL 5. 总结

1. 了解下什么是EGL?

EGL是什么?EGL是渲染API(如OpenGL, OpenGL ES, OpenVG)和本地窗口系统之间的接口。它处理图形上下文管理,表面/缓冲区创建,绑定和渲染同步,并使用其他Khronos API实现高性能,加速,混合模式2D和3D渲染OpenGL / OpenGL ES渲染客户端API OpenVG渲染客户端API原生平台窗口系统。这个稍微了解下就OK,你只要知道他是一个用来给OpenGl ES提供绘制界面的接口就可以了。

EGL的作用:
1. 与设备的原生窗口系统通信。
2. 查询绘图表面的可用类型和配置。
3. 创建绘图表面。
4. 在OpenGL ES 和其他图形渲染API之间同步渲染。
5. 管理纹理贴图等渲染资源。

这里关于EGL的介绍就讲这么多,如果你有兴趣的话你可以继续到这里去see yi seeUnderstanding Android EGL

2. EGL和OpenGl ES的关系

从上面的讲解我们基本上可以知道,EGL是为OpenGl提供绘制表面的。对的,这就是OpenGl ES数据渲染画布所在了。想必到这里大家也清楚了渲染数据去处的问题了。EGL还有什么用呢?EGL可以理解为OpenGl ES ES和设备之间的桥梁。对,完全可以这么理解。

3. EGL绘图的基本步骤

先上图,再说话。
简单讲解下各部分的作用:
1. Display(EGLDisplay) 是对实际显示设备的抽象。
2. Surface(EGLSurface)是对用来存储图像的内存区FrameBuffer 的抽象,包括Color Buffer,Stencil Buffer,Depth Buffer。
3. Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息。

EGL的基本使用步骤:
1. 首先我们需要知道绘制内容的目标在哪里,EGLDisplayer是一个封装系统屏幕的数据类型,通常通过eglGetDisplay方法来返回EGLDisplay作为OpenGl ES的渲染目标,eglGetDisplay()

 if ( (mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)) == EGL14.EGL_NO_DISPLAY) {
                throw new RuntimeException("unable to get EGL14 display");
            }
  1. 初始化显示设备,第一参数代表Major版本,第二个代表Minor版本。如果不关心版本号,传0或者null就可以了。初始化与 EGLDisplay 之间的连接:eglInitialize()
            if (!EGL14.eglInitialize(mEGLDisplay, 0, 0)) {
                throw new RuntimeException("unable to initialize EGL14");
            }
  1. 下面我们进行配置选项,使用eglChooseConfig()方法,Android平台的配置代码如下:
int[] attribList = {
                    EGL14.EGL_RED_SIZE, 8,
                    EGL14.EGL_GREEN_SIZE, 8,
                    EGL14.EGL_BLUE_SIZE, 8,
                    EGL14.EGL_ALPHA_SIZE, 
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.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.media.MediaPlayer; import android.net.Uri; import android.opengl.GLES30; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.provider.MediaStore; import android.util.Log; import android.util.Size; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; 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.CameraRenderer; 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; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class MainActivity extends AppCompatActivity { private static final String TAG = "camera2api"; private static final int REQUEST_CAMERA_PERMISSION = 100; private TextureView textureView; private GLSurfaceView glSurfaceView; 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 ImageReader imageReader; private boolean isTextureAvailable = false; // 跟踪Surface状态 private CameraManager manager; private volatile boolean isCapturing = false; private StreamConfigurationMap map; private long lastClickTime = 0; private static final long MIN_CLICK_INTERVAL = 1000; // 最小点击间隔1秒 private File file; private ContentResolver resolver; private ContentValues values; private Uri imageUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "onCreate ——————————————————————"); captureButton = findViewById(R.id.btnCapture); // textureView = findViewById(R.id.textureView); glSurfaceView = findViewById(R.id.glsurfaceView); glSurfaceView.setEGLContextClientVersion(3); // 使用 OpenGL ES 3.0 glSurfaceView.setRenderer(new CameraRenderer()); //初始化相机参数 initCamera(); //设置预览监听 glSurfaceView textureView.setSurfaceTextureListener(surfaceTextureListener); //设置拍照预监听 captureButton.setOnClickListener(v -> {//防止 连点抛出异常 long currentTime = SystemClock.elapsedRealtime(); if (currentTime - lastClickTime > MIN_CLICK_INTERVAL) { lastClickTime = currentTime; takePicture(); } else { Log.d(TAG, "点击过快,已忽略"); } }); } @Override protected void onResume() { Log.d(TAG, "onResume —————————————————————— "); Log.d(TAG, Log.getStackTraceString(new Throwable())); super.onResume(); glSurfaceView.onResume(); // 必须添加 // 检查相机权限 // ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA):用于检测this(当前应用)是否有Manifest.permission.CAMERA权限 //有则返回PackageManager.PERMISSION_GRANTED(有权限),没有则返回PackageManager.PERMISSION_DENIED(无权限) if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有相机权限——>开始请求相机权限"); //ActivityCompat.requestPermissions():请求权限的方法 //this:当前应用 //new String[]{权限1,权限2,...}:请求的权限集合 //code:对应编码名,发送请求后会调用回调函数 onRequestPermissionsResult(),该回调的第一个参数就是这个code,用于给我们做请求权限后的自定义操作。 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } //注意:以上的权限请求是异步操作 startBackgroundThread(); // 如果Surface已经可用,重新打开相机 if (textureView.isAvailable()) { openCamera(); } } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause ——————————————————————"); glSurfaceView.onPause(); // 必须添加 // 1. 仅在“非拍照中”且“会话存在”时,停止预览的重复请求(避免授权时资源被清) if (!isCapturing && cameraCaptureSession != null) { try { // 停止预览的重复请求(暂停预览画面),但不关闭会话和相机设备 cameraCaptureSession.stopRepeating(); Log.d(TAG, "onPause: 暂停预览重复请求(核心资源未释放)"); } catch (CameraAccessException e) { Log.e(TAG, "onPause: 停止预览失败", e); } // 2. 拍照中时,延迟处理(避免中断拍照流程) 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 onStop() { super.onStop(); Log.d(TAG, "onStop: Activity 完全不可见(如按Home键),释放核心资源"); // 1. 检查是否正在销毁(如果是销毁,交给 onDestroy 处理,避免重复释放) if (isFinishing()) { Log.d(TAG, "onStop: Activity 正在销毁,核心资源由 onDestroy 处理"); return; } // 2. 释放相机核心资源(会话、设备、ImageReader),但保留后台线程(onResume 可快速恢复) if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; Log.d(TAG, "onStop: 关闭 CameraCaptureSession"); } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; Log.d(TAG, "onStop: 关闭 CameraDevice"); } if (imageReader != null) { imageReader.close(); imageReader = null; Log.d(TAG, "onStop: 关闭 ImageReader"); } isSessionClosed = true; } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy: Activity 彻底销毁,释放所有资源"); // 1. 释放相机相关资源(防止内存泄漏) if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } if (imageReader != null) { imageReader.close(); imageReader = null; } // 2. 停止后台线程(彻底销毁,避免线程泄漏) stopBackgroundThread(); // 3. 置空所有引用(帮助 GC 回收) textureView = null; captureButton = null; manager = null; resolver = null; values = null; imageUri = null; backgroundHandler = null; backgroundThread = null; Log.d(TAG, "onDestroy: 所有资源释放完成"); } private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { //触发时机:SurfaceTexture 初始化后 @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { isTextureAvailable = true; // 设置可用标志 Log.d(TAG, "Surface就绪,触发相机初始化"); if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { openCamera(); } } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + "x" + height); } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { Log.d(TAG, "onSurfaceTextureDestroyed"); isTextureAvailable = false; if (cameraCaptureSession != null) { try { cameraCaptureSession.stopRepeating(); } catch (CameraAccessException e) { Log.e(TAG, "停止预览失败", e); } } return true; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } }; private void initCamera() { Log.d(TAG, "initCamera: 初始化相机配置"); try { //1.初始化CameraManager,获取相机配置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为空!!"); } //2.图片保存参数配置 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()); } } 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]; } 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 void openCamera() { if (!isTextureAvailable || textureView.getSurfaceTexture() == null) { // 检查Surface是否可用 Log.w(TAG, "Surface不可用,延迟打开相机,100ms后重试"); backgroundHandler.postDelayed(this::openCamera, 10000); 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()); } } 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; } }; //2.1 开始配置预览流 private void createCameraPreviewSession() { if (cameraDevice == null || !isTextureAvailable) { // 双重检查 Log.e(TAG, "创建预览会话失败: 相机或Surface不可用"); return; } try { //1.1 配置预览尺寸 // Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight()); // Log.i(TAG, "选择预览尺寸: " + previewSize.getWidth() + "x" + previewSize.getHeight()); // // //1.2 配置预览Surface // SurfaceTexture previewTexture = textureView.getSurfaceTexture(); // previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); // Surface previewSurface = new Surface(previewTexture); // GLSurfaceView 配置 Surface glSurface = glSurfaceView.getHolder().getSurface(); glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 创建会话时使用 GLSurfaceView 的 Surface cameraDevice.createCaptureSession( Collections.singletonList(glSurface), // 关键修改 sessionCallback, backgroundHandler ); // 2.配置拍照Surface //优化尺寸选择(兼容性处理) 配置jpegSizes Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size captureSize = chooseOptimalSize(jpegSizes); Log.i(TAG, "使用拍照尺寸: " + captureSize.getWidth() + "x" + captureSize.getHeight()); // 优化:检查尺寸变化,仅当尺寸变更时重建 if (imageReader == null || imageReader.getWidth() != captureSize.getWidth()) { if (imageReader != null) imageReader.close(); imageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2 // 缓冲区数量 ); } // 3.配置双输出Surface(预览 + 拍照) List<Surface> outputSurfaces = new ArrayList<>(2); outputSurfaces.add(previewSurface); outputSurfaces.add(imageReader.getSurface()); //3.创建会话 cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "2.2 预览会话配置成功"); cameraCaptureSession = session; try { //3.下发请求添加预览流 captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(previewSurface); //配置预览 自动对焦 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(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show(); } }, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "创建预览会话异常: " + e.getMessage()); } } // 优化尺寸选择 private Size chooseOptimalSize(Size[] choices) { // 优先选择16:9的比例 final double ASPECT_RATIO = 16.0 / 9.0; Size optimalSize = null; for (Size size : choices) { double ratio = (double) size.getWidth() / size.getHeight(); if (Math.abs(ratio - ASPECT_RATIO) <= 0.1) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } // 若无匹配则选择最大尺寸 if (optimalSize == null) { for (Size size : choices) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } return optimalSize != null ? optimalSize : new Size(1920, 1080); } 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, "用户授予相机权限"); } } } private void startBackgroundThread() { if (backgroundThread == null) { backgroundThread = new HandlerThread("CameraBackground"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); Log.d(TAG, "后台线程启动"); } } 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 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 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; } private void takePicture() { Log.i(TAG, "4.开始拍照流程——————————"); try { //1.检查是否可以拍照 boolean checkFlag = checkTakePicture(); if (!checkFlag) { Log.i(TAG, "拍照流程————检查未通过!退出拍照!"); return; } // 2. 创建拍照请求 CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); //3.设置拍照下发参数 //自动曝光 captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); // 图片旋转角度跟随手机默认 int rotation = getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotation); // 4. 图像可用监听器 imageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "图像数据可用"); try (Image image = reader.acquireLatestImage()) { if (image != null) { // 9. 创建目标文件 file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "pic_" + System.currentTimeMillis() + ".jpg" ); ByteBuffer buffer = image.getPlanes()[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); } } catch (Exception e) { Log.e(TAG, "保存图像错误: " + e.getMessage()); } }, backgroundHandler); // 11. 拍照回调 CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { // 处理拍照完成 isCapturing = false; // 可以在这里进行后续操作,但注意检查资源是否仍然有效 super.onCaptureCompleted(session, request, result); Log.i(TAG, "拍照完成,保存至: " + file.getAbsolutePath()); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存至: " + file, Toast.LENGTH_SHORT).show() ); createCameraPreviewSession(); // 恢复预览 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); Log.e(TAG, "拍照失败: " + failure.getReason()); } }; // 12. 执行拍照(添加会话状态检查) Log.d(TAG, "停止预览"); cameraCaptureSession.stopRepeating(); Log.d(TAG, "播放黑闪动画"); flash_mode(); Log.d(TAG, "4.下发拍照"); isCapturing = true; cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); } catch (CameraAccessException | IllegalStateException | SecurityException e) { Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e); } } //播放黑闪动画 private void flash_mode() { // 在拍照按钮点击事件中触发 View flashView = findViewById(R.id.flashview); flashView.setVisibility(View.VISIBLE); // 先显示黑色视图 // 创建动画:透明度从0到1再到0,模拟闪烁 AlphaAnimation flashAnim = new AlphaAnimation(0.0f, 1.0f); flashAnim.setDuration(100); // 动画时长100ms,根据需求调整 flashAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { flashView.setVisibility(View.INVISIBLE); // 动画结束后隐藏视图 // 在此处调用实际拍照逻辑(如Camera API) } @Override public void onAnimationRepeat(Animation animation) { } }); flashView.startAnimation(flashAnim); } } class CameraRenderer2 implements GLSurfaceView.Renderer { private SurfaceTexture cameraSurfaceTexture; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // 创建 OES 纹理 int[] texIds = new int[1]; GLES30.glGenTextures(1, texIds, 0); cameraSurfaceTexture = new SurfaceTexture(texIds[0]); // 初始化相机 initCameraWithSurface(new Surface(cameraSurfaceTexture)); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { } @Override public void onDrawFrame(GL10 gl) { cameraSurfaceTexture.updateTexImage(); // 更新纹理 // 执行自定义渲染管线... } private void initCameraWithSurface(Surface surface) { // 使用传入的 Surface 创建相机会话 cameraDevice.createCaptureSession( Collections.singletonList(surface), sessionCallback, backgroundHandler ); } } 我没听懂你让写的,你直接帮我修改
最新发布
09-20
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值