Android MediaCodec转码相关问题
最近在测试各种编码的性能的时候涉及到了android硬编,由于测试的指标有PSNR、SSIM等质量指标,中间遇到了一些坑,稍微记录一下。
硬编的常见用法
大多数实际工程中,硬解或者硬编都是通过Surface中转来做的,经过Surface我们可以做很多事情,比如这个Surface可以是SurfaceView的Surface,或者我们想用Opengl作一些事情,加滤镜或者编辑视频,Surface的方式都提供了方便的解决方式,这也是我们搜索MediaCodec,最常见的使用方式。bigflake(http://bigflake.com/mediacodec)列举出了很多示例代码,其中有一个google CTS代码DecodeEditEncode对于Surface的使用给出了很好的例子。
如果我们想做一个硬解硬编的加速转码(至少相较ffmpeg软解软编还是快一些的),可以参考github项目android-transcoder,这里给出了比较完整的transcode的示例,但是真正想用起来还是需要不少改动的,比如给出的Strategy只支持几个简单的比例,Cancel接口有些问题,这里不具体说了。
我最初测试的有一个很主要的目的是测试PSNR等指标,最初使用了ypresto的demo,但是最终得到的结果总是特别差,比如x264转码psnr能维持在40db的情况,经过Surface android硬解然后硬编基本智能再25db左右(我们最终对比PSNR是将结果mp4解码为YUV,然后直接再YUV三个channel上计算平均PSNR)。但是肉眼看起来其实硬编结果和x264甚至和原视频相差并不大,
硬编转码PSNR很低的问题所在
想到自己平时写shader用gpu加速来进行yuv转rgb的时候用到了yuv转RGB的转换公式,可以参考另外一篇记录color space2中YUV families的介绍,或者直接看一个综合的整理的bt601 & bt709 color matrix,比如bt601 tv range的YUV转RGB的公式为:
bt601 tv range RGB2YUV
看一下这里的YUV2RGB的转换公式,得到的RGB是可能有负值的,比如0, 255, 255得到的RGB就是负值,这里可能有人会说这个公式是针对tv range(也有叫studio range的,就是Y 16-235,UV 16-240),我说一下原因:
- 1,很多情况下尤其是国内的很多厂商录制出来的视频根本不带着color range信息,即使带着,经过各种渠道的转存流转,基本什么信息也都没有了
- 2,实际上硬解中是有KEY_COLOR_RANGE参数的,能设置两种值COLOR_RANGE_FULL和COLOR_RANGE_LIMITIED,就是对应着full range和tv range,但是更坑爹的是很多国内厂商根本没有做实现。这里表扬一下良心华为,录制出来的视频都有着完整的color range、color space、color transfer信息,华为录制出来的基本都是tv range视频,在<=480p的时候使用bt601,否则使用HDTV标准bt709,苹果录制出来的m4v文件也是比较良心的。测试了vivo x7录制出来的视频这几个信息都是空着的,难道公司的主要精力都放在了“2000W柔光自拍”上?
- 3,即使我们使用tv range内的YUV值,比如(16,240,240)也会算出来的Green是负值。其实tv range中的很多YUV值都会算出来RGB值是负的
回归正题,我们显示任何东西,最终都是以RGB的形式显示的,也就是说我们在Surface中的显示也是RGBA的color space,但是实际上上RGBA的值都必须是正值,负值是无法显示的,所以我们自己写shader的时候或者直接交给Surface处理的时候,需要对RGBA的值做clamp处理,裁剪到0-255范围内,也就是负值变成0,超过255的变成255。然后我们对这个clamp以后的RGB值如果再转回YUV值,就跟原始的YUV值相差比较大了。有人会觉得这样不会变色了吗?其实并不会!举个例子:
以yuv(0,0,0)为例(也可以拿16,240,240试试)
1. 先减128 。 (0,-128,-128)
2. 转换为RGB并作clamp RGB (-179.456,135.424,-226.816)->(0,135,0)
3. 转YUV (79.245,-44.685,-56.565)
4. 色度加128 YUV(79.245,83.315,71.435)
5. 取整,YUV(79,83,71) 转换完毕的值
再转回去:
6. 先减128 YUV (79,-45,-57)
7. 转换为RGB并作clamp RGB (-0.914,135.178,-0.74)->(0,135,0)
我们发现颜色是一样的,再转一下,看看YUV还会不会变化
8. 转YUV (79.245,-44.685,-56.565)
9. 色度加128 YUV(79.245,83.315,71.435)
10. 得到YUV(79,83,71) 转换完毕的值
上面这个例子我们发现(0,0,0) (79,83,71)实际对应的RGB是一样的,也就是人眼看起来是一样的。但是这样的处理过程对我们比较PSNR造成了很大的问题,计算出来的PSNR很低。x264进行转码却不会有这个问题,还没有自习看ff和x264转码的源码,有待确认具体是怎么个实现,猜测是没有对RGB做clamp,或者根本没有中转RGB。
其他方式做转码
在api 21后,android提供了Image类来做硬解的输出和硬编的输入,Image类基本类似于ffmpeg的AVFrame,里面包含帧图像的各种信息和各个plane,以及stride、width、height、crop x、crop y等信息,也就是说我们可以从Image中得到decode以后的YUV raw data,我们可以这样的到decode 出来的Image:
int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeout);
Image image = mDecoder.getOutputImage(result);
对于decode以后直接存储为YUV raw data file可以参考VideoToFrames github code(对应的博客是android高效解码得到YUV file)
未完待续
References
- Image class & YUV_420_888
- YUV_420_888 convert to NV21 & I420
- Anroid MediaCodec stuffs
- VideoEncoderDecoderTest
- Camera and MediaCodec colorspace not match, with images, stack over flow issue
- color format of camera and mediacodec, google disscuss
- CTS samples EncodeDecodeTest
- How to get stride of encoder
- ffmpeg command: how to convert to YUV file and how to display it
- VideoToYUVFrames github sample code
- Android MediaCodec transcoder sample demo, with surface
- NV21 NV12 I420 YV12
- CTS codecUtils
- google CTS
- YUV RGB convert
- android mediacodec color formats
- CTS code: DecodeEditEncodeTest
网友评论