OpenGL特效視頻編碼保存到本地出現紅屏、藍屏、黃屏問題的分析解決程序記錄
- 一、問題的描述
- 需求
- 問題
- 二、問題的分析
- 原理
- 三、問題的解決
- 分析
- 解決
- 四、問題的總結
- 總結
一、問題的描述
需求
在處理OpenGL視頻特效專案問題時,需求是要將特效處理完后的視頻外加音頻保存至本地的錄播功能,通過API glReadPixels拿到對應的RGBA視頻資料編碼至H264然后用AudioRecord錄音并編碼至AAC最終再通過MediaMuxer將兩者合成寫到本地,完成mp4視頻的錄制,當然如果是要推流則可以將資料通過PTS、DTS同步后直接推送即可
問題
拿到RGBA資料之后將資料通過Google開源的libyuv進行縮放、旋轉、鏡像、編碼等功能,首先是將其轉碼至I420然后進行縮放、旋轉、鏡像等操作,再將其還原到NV12或NV21最后將其通過硬編碼(產商限制只考慮使用硬編碼)生成mp4檔案,雖然編碼之后的視頻畫面是正常的,但是畫面上還有一層紅色,嘗試其他方式進行編碼后又遇到了藍色、黃色,效果如下圖所示

二、問題的分析
原理
RGBA用四個位元組(32bit)表示,分別代表Red、Green、Blue、Alpha,當然android提供的RGB格式也是很多的,常見的有RGB_8888、RGB_24、RGB_565等,需要注意的是資料源和編碼時的取資料順序,如果資料對不上,也就是大小端差異將會導致上述的紅、黃、藍屏問題,因為通道對不上,所以導致的這些色調問題
Android中最常見的硬編碼顏色格式有兩種,分別是COLOR_FormatYUV420Planar和COLOR_FormatYUV420SemiPlanar下面會介紹他們的yuv格式和yuv的具體位置存盤規則,如下圖所示

在上層Android開發中,我們根據RGBA字面意思的理解就是RGBARGBARGBA這種順序,但如果拿到的緩沖區資料不是如此恰好開發者按這個順序去編解碼的話就會出現色調問題,這里也順帶提一下libyuv的RGBA順序是小端模式,Android輸入的RGBA則是大端,如:Android中RGBA按大端順序輸入的話但在libyuv則處理的時候則為小端,即色道是相反的ABGR,這一點在處理的時候一定要注意,如果不確定資料源格式最好嘗試其它的編解碼函式
三、問題的解決
分析
剛開始遇到這個問題的時候,筆者猜測的原因可能是編碼引數沒設定對導致的,然后檢查引數又沒有問題,便開始撰寫各種JNI介面進行嘗試,但依舊沒有任何進展,最后一個朋友建議我把通道順序調換試試,剛開始沒有效果,也不知道是不是筆者JNI底層代碼寫的有問題,然后筆者開始去檢查自己的C++代碼
解決
筆者首先把封裝的I420旋轉、縮放、鏡像功能全部去掉,直接呼叫Libyuv的RGBA轉碼NV12、NV21函式,為了方便快速除錯和驗證問題,筆者封裝了一個通用函式來做校驗,把所有RGBA轉換到NV12和NV21格式的函式封裝到一個通用函式中,呼叫時配置一個type即可實作動態呼叫函式,非常快捷高效,代碼封裝如下
/**
* openGL rgba pixels data convert to yuv420sp(NV12/NV21) common func define
* @return func
*/
static int
(*rgbaToYuv420spFunc[])(const uint8 *, int, uint8 *, int, uint8 *, int, int, int) ={
libyuv::ARGBToNV12, libyuv::ARGBToNV21
};
為了快速驗證問題,函式具體的處理邏輯筆者沒有過多的去了解,下面貼上呼叫代碼
/**
*
* openGL rgba pixels data convert to android yuv420sp(NV12/NV21) func define
* @param env jni env
* @param rgba openGL rgba pixels data
* @param rgba_stride rgba stride
* @param yuv yuv data
* @param width src width
* @param height src height
* @param func func type
* @return result > 0 means encode success
*/
int rgbaToYuv420sp(JNIEnv *env, jbyteArray rgba, jint rgba_stride,
jbyteArray yuv, jint width, jint height,
int (*func)(const uint8 *, int, uint8 *, int, uint8 *, int, int, int)) {
size_t ySize = (size_t) (width * height);
jbyte *rgbaData = env->GetByteArrayElements(rgba, JNI_FALSE);
jbyte *yuvData = env->GetByteArrayElements(yuv, JNI_FALSE);
int ret = func((const uint8 *) rgbaData, rgba_stride, (uint8 *) yuvData, width,
(uint8 *) (yuvData + ySize), width, width, height);
env->ReleaseByteArrayElements(rgba, rgbaData, JNI_OK);
env->ReleaseByteArrayElements(yuv, yuvData, JNI_OK);
return ret;
}
寫好C++處理函式后,接下來就是JNI方法的封裝了
/**
* jni method define
* openGL rgba pixels data convert to android yuv420sp(NV12/NV21) , android support yuv420p(I420/YV12) and yuv420sp(NV12/NV21)
* @param env jni env
* @param clazz java class object
* @param type func type
* @param rgba openGL rgba pixels data
* @param yuv yuv420sp-nv12
* @param width data width
* @param height data height
* @return result > 0 means encode success
*/
int
Jni_RgbaToYuv420sp(JNIEnv *env, jclass clazz, int type, jbyteArray rgba, jbyteArray yuv, jint width,
jint height) {
// func type
uint8 cType = (uint8) (type & 0x0F);
// rgba use 4 byte
int rgba_stride = 4 * width;
// call c++ func
return rgbaToYuv420sp(env, rgba, rgba_stride, yuv, width, height, rgbaToYuv420spFunc[cType]);
}
Java呼叫處代碼
if (mVideoColorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
&& mVideoColorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar)
return null;
byte[] yuvData = new byte[width * height * 3 / 2];
switch (mVideoColorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
YuvUtils.RgbaToI420(Key.ARGB_TO_I420, rgba, yuvData, width, height);
return yuvData;
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
YuvUtils.RgbaToYuv420sp(Key.ARGBToNV12, rgba, yuvData, width, height);
return yuvData;
default:
throw new IllegalStateException("Unsupported color format!");
}
Key中定義的是對應C++函式定義的type,在底層根據type去選擇性的呼叫想要呼叫的函式,高效快捷的除錯代碼
經過上述的處理后,錄制的視頻就沒有再出現色調問題了,如下圖

可以看到mp4視頻畫面已經正常了,但是還存在鏡像問題,不過這都是小問題,問題不大!截圖是視頻中隨機截的一張,使用的是測驗手機拍攝另一個手機,至此問題已經解決
四、問題的總結
總結
在遇到問題的時候我們首先要列舉一系列的可能導致的原因,然后一一的去進行驗證,切記不要胡亂猜測那樣只會增加你的作業范圍而且有可能做很多無用功,也可以嘗試詢問朋友等,畢竟每個人的作業領域和知識廣度不一樣,都有自己擅長的地方,好了今天的博客就分享到此,有類似問題的老鐵也可以借鑒和參考本篇文章,希望能給您帶來幫助!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/298141.html
標籤:其他
