Android MediaCodec轉(zhuǎn)碼相關(guān)問題
最近在測試各種編碼的性能的時候涉及到了android硬編,由于測試的指標有PSNR、SSIM等質(zhì)量指標,中間遇到了一些坑,稍微記錄一下。
硬編的常見用法
大多數(shù)實際工程中,硬解或者硬編都是通過Surface中轉(zhuǎn)來做的,經(jīng)過Surface我們可以做很多事情,比如這個Surface可以是SurfaceView的Surface,或者我們想用Opengl作一些事情,加濾鏡或者編輯視頻,Surface的方式都提供了方便的解決方式,這也是我們搜索MediaCodec,最常見的使用方式。bigflake(http://bigflake.com/mediacodec)列舉出了很多示例代碼,其中有一個google CTS代碼DecodeEditEncode對于Surface的使用給出了很好的例子。
如果我們想做一個硬解硬編的加速轉(zhuǎn)碼(至少相較ffmpeg軟解軟編還是快一些的),可以參考github項目android-transcoder,這里給出了比較完整的transcode的示例,但是真正想用起來還是需要不少改動的,比如給出的Strategy只支持幾個簡單的比例,Cancel接口有些問題,這里不具體說了。
我最初測試的有一個很主要的目的是測試PSNR等指標,最初使用了ypresto的demo,但是最終得到的結(jié)果總是特別差,比如x264轉(zhuǎn)碼psnr能維持在40db的情況,經(jīng)過Surface android硬解然后硬編基本智能再25db左右(我們最終對比PSNR是將結(jié)果mp4解碼為YUV,然后直接再YUV三個channel上計算平均PSNR)。但是肉眼看起來其實硬編結(jié)果和x264甚至和原視頻相差并不大,
硬編轉(zhuǎn)碼PSNR很低的問題所在
想到自己平時寫shader用gpu加速來進行yuv轉(zhuǎn)rgb的時候用到了yuv轉(zhuǎn)RGB的轉(zhuǎn)換公式,可以參考另外一篇記錄color space2中YUV families的介紹,或者直接看一個綜合的整理的bt601 & bt709 color matrix,比如bt601 tv range的YUV轉(zhuǎn)RGB的公式為:


看一下這里的YUV2RGB的轉(zhuǎn)換公式,得到的RGB是可能有負值的,比如0, 255, 255得到的RGB就是負值,這里可能有人會說這個公式是針對tv range(也有叫studio range的,就是Y 16-235,UV 16-240),我說一下原因:
- 1,很多情況下尤其是國內(nèi)的很多廠商錄制出來的視頻根本不帶著color range信息,即使帶著,經(jīng)過各種渠道的轉(zhuǎn)存流轉(zhuǎn),基本什么信息也都沒有了
- 2,實際上硬解中是有KEY_COLOR_RANGE參數(shù)的,能設(shè)置兩種值COLOR_RANGE_FULL和COLOR_RANGE_LIMITIED,就是對應(yīng)著full range和tv range,但是更坑爹的是很多國內(nèi)廠商根本沒有做實現(xiàn)。這里表揚一下良心華為,錄制出來的視頻都有著完整的color range、color space、color transfer信息,華為錄制出來的基本都是tv range視頻,在<=480p的時候使用bt601,否則使用HDTV標準bt709,蘋果錄制出來的m4v文件也是比較良心的。測試了vivo x7錄制出來的視頻這幾個信息都是空著的,難道公司的主要精力都放在了“2000W柔光自拍”上?
- 3,即使我們使用tv range內(nèi)的YUV值,比如(16,240,240)也會算出來的Green是負值。其實tv range中的很多YUV值都會算出來RGB值是負的
回歸正題,我們顯示任何東西,最終都是以RGB的形式顯示的,也就是說我們在Surface中的顯示也是RGBA的color space,但是實際上上RGBA的值都必須是正值,負值是無法顯示的,所以我們自己寫shader的時候或者直接交給Surface處理的時候,需要對RGBA的值做clamp處理,裁剪到0-255范圍內(nèi),也就是負值變成0,超過255的變成255。然后我們對這個clamp以后的RGB值如果再轉(zhuǎn)回YUV值,就跟原始的YUV值相差比較大了。有人會覺得這樣不會變色了嗎?其實并不會!舉個例子:
以yuv(0,0,0)為例(也可以拿16,240,240試試)
1. 先減128 。 (0,-128,-128)
2. 轉(zhuǎn)換為RGB并作clamp RGB (-179.456,135.424,-226.816)->(0,135,0)
3. 轉(zhuǎn)YUV (79.245,-44.685,-56.565)
4. 色度加128 YUV(79.245,83.315,71.435)
5. 取整,YUV(79,83,71) 轉(zhuǎn)換完畢的值
再轉(zhuǎn)回去:
6. 先減128 YUV (79,-45,-57)
7. 轉(zhuǎn)換為RGB并作clamp RGB (-0.914,135.178,-0.74)->(0,135,0)
我們發(fā)現(xiàn)顏色是一樣的,再轉(zhuǎn)一下,看看YUV還會不會變化
8. 轉(zhuǎn)YUV (79.245,-44.685,-56.565)
9. 色度加128 YUV(79.245,83.315,71.435)
10. 得到Y(jié)UV(79,83,71) 轉(zhuǎn)換完畢的值
上面這個例子我們發(fā)現(xiàn)(0,0,0) (79,83,71)實際對應(yīng)的RGB是一樣的,也就是人眼看起來是一樣的。但是這樣的處理過程對我們比較PSNR造成了很大的問題,計算出來的PSNR很低。x264進行轉(zhuǎn)碼卻不會有這個問題,還沒有自習看ff和x264轉(zhuǎn)碼的源碼,有待確認具體是怎么個實現(xiàn),猜測是沒有對RGB做clamp,或者根本沒有中轉(zhuǎn)RGB。
其他方式做轉(zhuǎn)碼
在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(對應(yīng)的博客是android高效解碼得到Y(jié)UV file)
未完待續(xù)
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