从 SurfaceView 获取 Bitmap

在做三摄算法集成项目的时候,因为手机有三个摄像头,所以存在两种不同的双摄人像模式,分别是 1 倍人像模式(以下简称 1x)和 2 倍光变人像模式(以下简称 2x)。我们没有在 App 层面使用同时打开两个 camera id 的方式来打开双摄,因为这样逻辑会稍微复杂,所以我们是在底层做了这件事,在上层我们只需要打开新增的 camera id,就可以同时打开两个摄像头。

由于项目需要,我在高通的 SnapdragonCamera App 的 Preview 中添加了 1x 和 2x 的切换按钮,效果类似 iPhone(双摄) 中 1x 和 2x 的切换。查看 SnapdragonCamera 的代码后,找到了一个简单的切换 Camera 的方式,就是直接修改 SharedPreferences 中的一个关于当前 Camera id 配置。

这么做之后,功能确实实现了,但是在切换的过程中,因为会释放资源再重新构建,所以预览会消失,在 UI 上看到的情况就是黑了一下。这种体验不怎么好,需要优化一下。经过和 PM 沟通后,最后决定的优化方案是在切换的过程中,在 UI 上展示上一次预览的最后一帧,并且加一个模糊效果。

在高通的 SnapdragonCamera 中使用的是 SurfaceView 进行 Preview,因为 SurfaceView 不像 TextureView 一样有 getBitmap() 方法,所以获取最后一帧这一点得想想怎么做。

一开我想的是在 Preview 的时候再加一个 ImageReader 或者 TextureView,但是这样会把问题搞的比较复杂,并且也不是很好的解决方案。所以还是想在现有的 SurfaceView 上做点文章。

通过上网查阅资料(这里再次吐槽一下百度,不管是使用中文还是英文,查资料都不太好使。推荐使用 Google 用英文关键字查询),发现了 PixelCopy 这个好东西。

PixelCopy 是在 API level 24(Android 7.0 Nougat)中添加的,由于我们这个项目使用的是最新的 Android 9 Pie,所以使用这个 API 是没有问题的。下面是官网对它的介绍:

Provides a mechanisms to issue pixel copy requests to allow for copy operations from Surface to Bitmap

PixelCopy 提供了一种发出像素复制请求的机制,允许从 Surface 到 Bitmap 的复制操作。Perfect,我们要的就是这样的一种功能。

下面是使用 PixelCopy 从 SurfaceView 获取 Bitmap 的示例代码。

Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Bitmap getBitmap(SurfaceView surfaceView) {
Bitmap bitmap = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(surfaceView, bitmap, new PixelCopy.OnPixelCopyFinishedListener() {
@Override
public void onPixelCopyFinished(int copyResult){
if (PixelCopy.SUCCESS == copyResult) {
// onSuccessCallback(bitmap)
} else {
// onErrorCallback()
}
}
}, surfaceView.getHandler());
}

Kotlin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override fun getBitmap(surfaceView: SurfaceView) = Single.create<Bitmap> {
val bitmap = Bitmap.createBitmap(surfaceView.width, surfaceView.height, Bitmap.Config.ARGB_8888)
val listener = PixelCopy.OnPixelCopyFinishedListener { copyResult ->
when (copyResult) {
PixelCopy.SUCCESS -> {
// onSuccessCallback(bitmap)
}
else -> {
// onErrorCallback()
}
}
}
PixelCopy.request(surfaceView, bitmap, listener, surfaceView.handler)
}

在 onPixelCopyFinished 回调中,我们的 Bitmap 已经从 SurfaceView 获取到了 Bitmap。

全文完

感谢阅读