@ltlovezh
2020-06-20T02:49:16.000000Z
字数 4997
阅读 3187
图形系统
最近遇到一个有趣的问题:通过MediaCodec解码带旋转角度的视频时,如果Output Surface是TextureView或者SurfaceView提供的,那么屏幕上的视频帧可以正常展示(处理了旋转角度);如果Surface是由SurfaceTexture创建而来,那么通过SurfaceTexture获取的OES纹理,是未处理旋转角度的,需要自己兼容下角度问题。
此外,我也测试了Camera,首先通过Camera.setDisplayOrientation设置顺时针旋转角度,然后设置Output Surface接收Camera预览视频帧。不出意外,也得到了同样的结论。
既然都是通过Surface接收视频帧,那为什么会存在这种差异那?MediaCodec和Camera生产视频帧,并且展示到屏幕上的流程如下所示:
MediaCodec和Camera会通过native_window_set_buffers_transform函数向Surface(ANativeWindow)设置Transform Flag,保存在Surface.mTransform变量。MediaCodec和Camera通过Surface持有的BufferQueueProducer向BufferQueue入队BufferItem时,会把Surface.mTransform赋值给BufferItem.mTransform。GLConsumer通过BufferQueueComsumer获取BufferItem后,根据BufferItem.mTransform,还原出一个4*4的纹理变换矩阵,保存在GLConsumer.mCurrentTransformMatrix变量中,GLConsumer的使用者可以通过GLConsumer.getTransformMatrix方法获取这个纹理变换矩阵。GLConsumer的使用者负责把纹理变换矩阵作用到纹理上,以正确展示视频帧。
GLConsumer负责把BufferItem.mGraphicBuffer转换为纹理,并且根据BufferItem.mTransform计算出纹理变换矩阵,此时的纹理是原始状态(未应用纹理矩阵)。获取纹理变换矩阵,并且应用到纹理,是GLConsumer(纹理)使用方的责任。
不管Surface是由TextureView提供还是通过SurfaceTexture创建,上述前3步都是相同的流程。
导致上述差异的根本原因是第4步:GLConsumer的使用者是否把纹理变换矩阵应用到纹理
当Surface由TextureView提供时,GLConsumer的使用者是DeferredLayerUpdater,它获取纹理变换矩阵后,会填充到Layer.texTransform变量(frameworks\base\libs\hwui\Layer.h),后续在硬件加速的异步渲染线程中,OpenGLRenderer渲染DrawLayerOp(基于上面的Layer创建而来)时,会应用该纹理变换矩阵。
当Surface由SurfaceView提供时,Surface是独立窗口,GLConsumer的使用者是frameworks/native/services/surfaceflinger/Layer,它获取纹理变换矩阵后,会填充到Layer.mTexture.mTextureMatrix中,后续SurfaceFlinger合成Layer时,会应用该纹理变换矩阵。
可见,当Output Surface由TextureView或者SurfaceView提供时,GLConsumer的使用者主动获取并使用了纹理变换矩阵,所以我们在屏幕上看到的视频帧才是正常的。
而当Output Surface由我们基于OES纹理ID创建的SurfaceTexture创建而来时,GLConsumer(纹理)的使用者是业务方,所以需要业务方通过SurfaceTexture.getTransformMatrix主动获取纹理变换矩阵,并把它应用到OES纹理上。即:我们拿到的OES纹理本就是原始纹理,需要应用纹理变换矩阵后,才能正常展示。
业务方要怎么使用纹理变换矩阵那?
SurfaceTexture.getTransformMatrix获取纹理变换矩阵,该矩阵是列优先次序存储,可以直接通过glUniformMatrix4fv函数上传到顶点着色器。我们看下旋转角度在MediaCodec和Camera中的流转流程。
通过MediaCodec解码视频时,需要通过MediaFormat设置解码参数,例如:SPS、PPS等。当视频存在旋转角度时,需要通过MediaFormat.KEY_ROTATION配置旋转角度,表示视频帧需要顺时针旋转多少度,才能正确展示。但是要注意的是:只有当MediaCodec直接解码到Surface时,旋转角度才有效。
旋转角度在MediaCodec中的流转流程如下所示:
MediaCodec::configure配置解码器,MediaFormat作为参数。kWhatConfigure消息,在自有线程配置解码器。ACodec->initiateConfigureComponent(MediaFormat为参数)配置ACodec。kWhatConfigureComponent消息在自有线程配置ACodec。ACodec::LoadedState::onConfigureComponent方法。ACodec.configureCodec方法,负责通过MediaFormat配置ACodec,其中从format中取出了"rotation-degrees",保存在ACodec.mRotationDegrees变量中。ACodec.setupNativeWindowSizeFormatAndUsage中调用全局函数setNativeWindowSizeFormatAndUsage为Surface(ANativeWindow)设置transform flag。核心代码在setNativeWindowSizeFormatAndUsage函数中:
// 根据旋转角度获得transform flagint transform = 0;if ((rotation % 90) == 0) {switch ((rotation / 90) & 3) {case 1: transform = HAL_TRANSFORM_ROT_90; break;case 2: transform = HAL_TRANSFORM_ROT_180; break;case 3: transform = HAL_TRANSFORM_ROT_270; break;default: transform = 0; break;}}// 为Surface(ANativeWindow)设置transformerr = native_window_set_buffers_transform(nativeWindow, transform);
native_window_set_buffers_transform函数会触发ANativeWindow子类Surface的perform方法处理NATIVE_WINDOW_SET_BUFFERS_TRANSFORM消息,接着是dispatchSetBuffersTransform -> setBuffersTransform,最后把transform保存在了Surface.mTransform变量中。
至此,旋转角度已经设置到了Surface.mTransform,后面就是Surface生产图像数据时负责使用它了。
Surface内部通过BufferQueueProducer入队图像数据时,会把Surface.mTransform赋值给BufferItem.mTransform,BufferQueue的元素就是BufferItem。
然后,消费端GLConsumer通过BufferQueueComsumer获取BufferItem后,根据BufferItem.mTransform,还原出一个4*4的纹理变换矩阵,保存在GLConsumer.mCurrentTransformMatrix变量中,GLConsumer的使用者可以调用GLConsumer.getTransformMatrix方法获取这个纹理变换矩阵,然后在使用对应纹理时,把该矩阵应用到纹理上。
旋转角度在Camera中的流转流程如下所示:
Camera.setDisplayOrientation设置预览显示时的顺时针旋转角度。Camera.sendCommand方法,这里的Camera是客户端,会通过Binder调用到服务端CameraClient::sendCommand。native_window_set_buffers_transform为Camera的Preview Window(Surface)设置旋转角度。Surface.mTransform了,这部分流程与MediaCodec类似。应该说MediaCodec和Camera设置旋转角度的流程不同,但是最终都是把旋转角度设置到Surface.mTransform,后面就是Surface和GLConsumer的事情了。
不管是MediaCodec还是Camera,都是Surface的图像数据来源,只不过这个源图像数据,可能有一定的旋转角度或者镜像。这种情况下,MediaCodec和Camera就会通过native_window_set_buffers_transform为Surface设置Transform Flag,保存在Surface.mTransform变量中,表示生成的图像数据,必须经过Transform变换之后才能正常显示。
// Transform Flagtypedef enum android_transform {// 水平镜像HAL_TRANSFORM_FLIP_H = 0x01,// 垂直镜像HAL_TRANSFORM_FLIP_V = 0x02,// 顺时针旋转90度HAL_TRANSFORM_ROT_90 = 0x04,// 顺时针旋转180度HAL_TRANSFORM_ROT_180 = 0x03,// 顺时针旋转270度HAL_TRANSFORM_ROT_270 = 0x07,// don't use. see system/window.hHAL_TRANSFORM_RESERVED = 0x08,} android_transform_t;
生产端Surface通过BufferQueueProducer添加图像数据时,会把Surface.mTransform赋值给BufferItem.mTransform,然后入队到BufferQueue。
消费端GLConsumer通过BufferQueueComsumer获取BufferItem后,根据BufferItem.mTransform Flag,还原出一个4*4的纹理变换矩阵,保存在GLConsumer.mCurrentTransformMatrix变量中,GLConsumer的使用者调用GLConsumer.getTransformMatrix获取这个纹理变换矩阵,然后作用到对应纹理上,就可以正确展示视频帧了。