跳转至

安卓如何实现 HDR 视频预览的高亮效果

随着移动设备硬件的不断发展,目前各大厂商的旗舰机型都支持 HDR 视频的拍摄和预览了,有的都已经支持杜比视界格式了。

HDR 视频预览,最亮眼的地方就是可以激发屏幕的高亮效果,在系统相册中播放 HDR 视频时会明显感觉到屏幕的亮度变化。

有些第三方软件也适配了这一特点,比如短视频在刷 Feed 流时突然一个视频很亮眼,可能就是刷到了 HDR 视频。

HDR 亮度值

当然这是靠我们的肉眼来确认刷到了 HDR 视频,那么如何去量化 HDR 视频的亮度值呢?

以小米机型为例,可以在开发者选项中打开:显示 HDR/SDR 比率,在屏幕右上角可以显示当前 HDR 亮度值,如果是 1.00 目前是 SDR 的状态。

Image title

到预览 HDR 视频值,数值会逐渐变大,直到稳定,同时屏幕亮度也会逐渐提高,直到稳定,此时的数值可以认为是当前的 HDR 亮度值。

其他机型可以参照去找找,以这个数值来作为 HDR 亮度值。

HDR 视频预览实现

Activity 级别的 HDR 效果

这是一种最低成本的实现方式,通过给当前 Activity 的 window.colorMode 设置为 ActivityInfo.COLOR_MODE_HDR ,可以让整个 Activity 都是 HDR 模式,同时也会更改 HDR 亮度值。

这种情况下,在 Activity 内播放视频,视频播放对应的 SurfaceView 可能并不会有高亮效果。

MediaCodec + Surface 级别的 HDR 效果

这是在 MediaCodec 解码器层面去实现的 HDR 效果,MedidaCodec 在创建初始化的时候需要传入一个 Surface ,假如这个 Surface 就是我们最终要渲染上屏的 Surface ,那么在解码器底层内部就会根据视频格式来决定是否要激发屏幕的高亮效果。

这种方式比较直接,如果只是播放视频,不需要对视频画面做二次处理的话,这种方式也是可行的。

ImageWriter + HardwareBuffer 级别的 HDR 效果

如果是需要对视频做二次处理,那通常都会引入 OpenGL 做渲染,而且也会引入 EGL 环境来上屏,最终是将纹理绘制到对应的 FBO 上来实现渲染上屏的。

这种情况下想要实现 HDR 效果,需要对渲染上屏的模块进行改造。

正常的 OpenGL 渲染环境下,是通过获取 SurfaceView 的 Surface 来创建 EGLSurface 对象,然后在 eglMakeCurrent 中关联到这个 EGLSurface 对象。

但现在,需要通过 SurfaceView 的 Surface 来创建 ImageWriter ,而不是创建 EGLSurface 对象了。

1
2
3
4
5
6
7
8
bt709_dataspace = DataSpace.pack(DataSpace.STANDARD_BT2020, DataSpace.TRANSFER_ST2084, DataSpace.RANGE_LIMITED);
bt2020_dataspace = DataSpace.pack(DataSpace.STANDARD_BT709, DataSpace.TRANSFER_SMPTE_170M, DataSpace.RANGE_LIMITED);
new ImageWriter.Builder(outputSurface)
                .setWidthAndHeight(outputSize.getWidth(), outputSize.getHeight())
                .setMaxImages(MAX_IMAGES)
                .setDataSpace(bt2020_dataspace)
                .setHardwareBufferFormat(format)
                .build();

创建 ImageWriter 时可以指定需要的 dataspace ,SDR 视频就用 BT709,HDR 视频就用 BT2020 。

由于没有创建 EGLSurface 对象,eglMakeCurrent 方法就直接传 EGL_NO_SURFACE 就行了。

eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);

接下来的渲染操作:

Image title
  1. 通过 ImageWriter 的 dequeueInputImage 方法拿到 Image 对象,从 Image 对象中得到 HardwareBuffer ,并通过 HardwareBuffer 构建 EGLImage 对象。

核心代码应该就是创建 EGLImageKHR 这块了。

1
2
3
4
5
6
7
8
9
EGLDisplay display = nullptr;
display = eglGetCurrentDisplay();
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
EGLImageKHR eglImageKHR{nullptr};  // android
eglImageKHR = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
                                                            clientBuffer, eglImageAttributes);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texId);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)eglImageKHR);
  1. 接下来就是实现 OpenGL 的正常渲染逻辑,将上屏纹理绘制到 FBO 上。

  2. 最后通过 ImageWriter 的 queueInputImage 方法将第一步拿到的 Image 对象进行上屏,从而实现绘制。

可以通过 Image 的 setDataSpace 方法让屏幕实现 HDR 高亮效果。如果 DataSpace 是 HLG 就会高亮,如果是 BT709 就不会。

也通过 SurfaceView 的 setDesiredHdrHeadroom 方法可以指定 HDR 的亮度值。

这种方式就比较适合对视频进行二次处理的场景了,而且改动也不大,既能兼容原有的 SDR 格式也可以拓展支持 HDR 预览。

小结

以上就是三种方式实现 HDR 视频预览的高亮效果,大多数情况下还是通用。可能会存在一些机型兼容性问题,需要具体问题具体分析了,可以联系我一起看看怎么个事...

评论