3. 设置图像格式
任务说明
本案例要求将摄像头输出格式设置为 NV21、分辨率 640x480,并验证设置是否生效。NV21 是 Android 相机默认支持的 YUV 格式,Y 平面在前,VU 交错平面在后。我们将通过 Camera2 API 实现配置,并在回调中检查实际输出格式与分辨率。
实现代码
// CameraFormatSetter.java
// 依赖:Android 5.0+ (API 21+),Camera2 API
import android.content.Context;
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.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.Collections;
public class CameraFormatSetter {
private CameraDevice cameraDevice;
private CameraCaptureSession captureSession;
private ImageReader imageReader;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private String cameraId;
private Size targetSize = new Size(640, 480);
private boolean formatVerified = false;
// 验证结果回调
public interface VerificationCallback {
void onVerified(boolean success, String message);
}
public void openCamera(Context context, VerificationCallback callback) {
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
try {
// 选择后置摄像头
for (String id : manager.getCameraIdList()) {
CameraCharacteristics chars = manager.getCameraCharacteristics(id);
Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
cameraId = id;
break;
}
}
if (cameraId == null) { callback.onVerified(false, "未找到后置摄像头"); return; }
// 检查 NV21 和 640x480 是否受支持
StreamConfigurationMap map = manager.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] nv21Sizes = map.getOutputSizes(ImageFormat.NV21);
boolean sizeSupported = false;
if (nv21Sizes != null) {
for (Size s : nv21Sizes) {
if (s.getWidth() == 640 && s.getHeight() == 480) {
sizeSupported = true;
break;
}
}
}
if (!sizeSupported) {
callback.onVerified(false, "摄像头不支持 NV21 640x480");
return;
}
// 启动后台线程
startBackgroundThread();
// 创建 ImageReader,格式为 NV21
imageReader = ImageReader.newInstance(640, 480, ImageFormat.NV21, 2);
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image != null) {
// 验证实际格式与分辨率
int actualFormat = image.getFormat();
int width = image.getWidth();
int height = image.getHeight();
String msg = "实际格式: " + actualFormat + " (NV21=" + ImageFormat.NV21
+ "), 分辨率: " + width + "x" + height;
boolean ok = (actualFormat == ImageFormat.NV21)
&& (width == 640) && (height == 480);
formatVerified = ok;
callback.onVerified(ok, msg);
image.close();
}
}
}, backgroundHandler);
// 打开摄像头
if (checkSelfPermission(context)) return;
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
cameraDevice = camera;
createCaptureSession(callback);
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
cameraDevice = null;
callback.onVerified(false, "摄像头打开失败,错误码: " + error);
}
}, backgroundHandler);
} catch (CameraAccessException e) {
callback.onVerified(false, "摄像头访问异常: " + e.getMessage());
}
}
private void createCaptureSession(VerificationCallback callback) {
try {
Surface surface = imageReader.getSurface();
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(surface);
cameraDevice.createCaptureSession(Collections.singletonList(surface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
captureSession = session;
try {
// 设置 NV21 格式(通过 ImageReader 已隐式指定)
// 设置分辨率(通过 ImageReader 已隐式指定)
// 提交请求
CaptureRequest request = builder.build();
session.setRepeatingRequest(request, null, backgroundHandler);
} catch (CameraAccessException e) {
callback.onVerified(false, "请求提交失败: " + e.getMessage());
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
callback.onVerified(false, "会话配置失败");
}
}, backgroundHandler);
} catch (CameraAccessException e) {
callback.onVerified(false, "创建请求失败: " + e.getMessage());
}
}
private void startBackgroundThread() {
backgroundThread = new HandlerThread("CameraBackground");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
public void closeCamera() {
try {
if (captureSession != null) { captureSession.close(); captureSession = null; }
if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; }
if (imageReader != null) { imageReader.close(); imageReader = null; }
if (backgroundThread != null) {
backgroundThread.quitSafely();
try { backgroundThread.join(); } catch (InterruptedException e) { }
backgroundThread = null;
backgroundHandler = null;
}
} catch (Exception e) { }
}
// 简化权限检查(实际需在 Activity 中处理)
private boolean checkSelfPermission(Context context) {
return false; // 实际项目中需检查 CAMERA 权限
}
}
调用方式
// 在 Activity 或 Fragment 中调用
CameraFormatSetter setter = new CameraFormatSetter();
setter.openCamera(this, new CameraFormatSetter.VerificationCallback() {
@Override
public void onVerified(boolean success, String message) {
// 在 UI 线程显示结果
runOnUiThread(() -> {
if (success) {
// 设置生效
Log.i("CameraFormat", "验证通过: " + message);
} else {
Log.e("CameraFormat", "验证失败: " + message);
}
});
}
});
// 页面销毁时释放
@Override
protected void onDestroy() {
super.onDestroy();
setter.closeCamera();
}
验证方法
运行应用后,观察 Logcat 中 CameraFormat 标签的输出。若打印 验证通过: 实际格式: 17 (NV21=17), 分辨率: 640x480,则说明设置生效。其中 17 是 ImageFormat.NV21 的常量值。若输出 验证失败 或摄像头不支持提示,则说明设备不支持该组合或设置未生效。
判定标准
- Logcat 中显示格式常量为 17(NV21),且宽高为 640x480,即判定为通过。
- 若设备不支持 NV21 640x480,代码会提前返回失败信息,此时应更换设备或调整目标参数。
- 实际预览画面无需显示,本案例仅验证格式配置是否正确。