最近我们公司的产品,在一款Android平板上遇到一个奇怪的问题,现象是本地视频预览画面显示是黑屏,监听VideoFrame无输出。下面我来描述一下这个故事。
在WebRTC Android SDK源码的Camera2Session.java这个文件里,有这样一段代码:
private class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
//...
@Override
public void onConfigured(CameraCaptureSession session) {
final CaptureRequest.Builder captureRequestBuilder =
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
//...
chooseStabilizationMode(captureRequestBuilder);
//...
}
//...
}
为了节省篇幅,我只摘取了关键的部分。
上面代码中,通过 createCaptureRequest 传递了常量 CameraDevice.TEMPLATE_RECORD。在Android的CameraDevice.java中,一共定义了6种TEMPLATE:
TEMPLATE_PREVIEW,
TEMPLATE_STILL_CAPTURE,
TEMPLATE_RECORD,
TEMPLATE_VIDEO_SNAPSHOT,
TEMPLATE_ZERO_SHUTTER_LAG,
TEMPLATE_MANUAL
通过实验,我把这里的 TEMPLATE_RECORD 换成了 TEMPLATE_PREVIEW,获得了更大的视野范围(相当于调小焦距),更改以后,放到了产品里一段时间,似乎也没遇到过什么问题(我们公司的产品是音视频实时通讯类的)。
但有一天我们有一个客户突然丢给我们一个平板(华为荣耀Waterplay),告诉我们说这个机器上,使用我们的产品无法显示自己的摄像头画面,而系统照相机是OK的。我第一时间就意识到是不是因为修改了这个Request template导致的?
问题的排查过程就不细说了,其实问题不是发生在 CameraDevice.createCaptureRequest() 这句上,而是在下方的 chooseStabilizationMode 这个函数里。下面是WebRTC中这个函数的实现:
// Prefers optical stabilization over software stabilization if available. Only enables one of
// the stabilization modes at a time because having both enabled can cause strange results.
private void chooseStabilizationMode(CaptureRequest.Builder captureRequestBuilder) {
final int[] availableOpticalStabilization = cameraCharacteristics.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
if (availableOpticalStabilization != null) {
for (int mode : availableOpticalStabilization) {
if (mode == CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON) {
captureRequestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE,
CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON);
captureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
Logging.d(TAG, "Using optical stabilization.");
return;
}
}
}
// If no optical mode is available, try software.
final int[] availableVideoStabilization = cameraCharacteristics.get(
CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
for (int mode : availableVideoStabilization) {
if (mode == CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON) {
captureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
captureRequestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE,
CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_OFF);
Logging.d(TAG, "Using video stabilization.");
return;
}
}
Logging.d(TAG, "Stabilization not available.");
}
代码写的很清楚,先检测设备是否支持光学视频稳定,支持的话启用之,函数返回。否则继续查询是否支持软件视频稳定,如果查询到支持的话,启用之。
虽然说我们公司有很多测试设备,但后来发现大多数设备要么是Camera1,不会走到Camera2里来,要么就是支持光学视频稳定,直接就在第一个if就返回了。唯独遇到华为荣耀Waterplay这个平板,是走到了第2个if,并且执行了关键的一句:
captureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
问题原因就是:当使用 TEMPLATE_PREVIEW 时,如果同时再尝试通过这一句开启软件视频稳定特性,就会出现我遇到的问题(预览黑屏、没有视频帧回调通知)!
通过翻看Android Developer,我找到了已经被标记为Deprecated的API: setVideoStabilization。它的描述是这样的:
Enables and disables video stabilization. Use isVideoStabilizationSupported() to determine if calling this method is valid.
Video stabilization reduces the shaking due to the motion of the camera in both the preview stream and in recorded videos, including data received from the preview callback. It does not reduce motion blur in images captured with Camera#takePicture.
Video stabilization can be enabled and disabled while preview or recording is active, but toggling it may cause a jump in the video stream that may be undesirable in a recorded video.
这里并没有描述可能出现什么兼容性问题呢……
导致这个问题的基本原因已经找到了,但这并不是根本原因。还有一些疑问摆在面前:
如果想再往下挖掘深层次的原因,就需要顺着 cameraCharacteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) 这句,去到Android源码中探索了,因为我手边没有Android的源代码,下载一份太费劲。或者到 AndroidXef 在线搜索也都是可以尝试的。有时间的话,再深入看一下。