10bit YUV(P010)的存儲結(jié)構(gòu)和處理

10bit YUV

前面討論關(guān)于 YUV 圖像 NV21、YUYV 等格式的處理入篮,都是 8 bit YUV 格式,即每個 Y媚赖、U午乓、V 分量分別占用 8 個 bit (一個字節(jié))剪个。

可以類比,10bit YUV 就是每個 Y罪郊、U蠕蚜、V 分量分別占用 10 個 bit ,但是實際處理中悔橄,我們是以字節(jié)為單位進行存儲和處理的靶累,所以最終處理的數(shù)據(jù)是以 2 個字節(jié)來存儲 10bit 的有效數(shù)據(jù)。

也就是說 10bit YUV 癣疟,每個像素( Y 分量)將占用 16bit 兩個字節(jié)尺铣,但是其中 6 個 bit 是 padding ,補 0 争舞。

10bit YUV 結(jié)構(gòu)

為什么要了解 10bit YUV ? 最近發(fā)現(xiàn)越來越多的視頻解碼出來是 10bit YUV 的圖像,毫無疑問 10bit YUV 會有更好的動態(tài)范圍澈灼,能表現(xiàn)出更豐富的顏色和更多的信息竞川。

隨著計算機處理信息的能力越來越厲害,這種能表現(xiàn)更高動態(tài)范圍的圖像存儲格式將會逐漸成為主流叁熔,但是現(xiàn)在很多算法都不能直接處理 10bit 的 YUV 委乌,都是先將其轉(zhuǎn)換為 8bit YUV ,然后再進行處理荣回,這實際上是丟棄了 10bit YUV 的圖像高動態(tài)范圍優(yōu)勢遭贸。

令人遺憾的是在渲染圖像時,目前 OpenGL 也無法直接對 10bit YUV 進行渲染心软,也是需要先轉(zhuǎn)換為 8bit YUV 壕吹。

接下來以一種常見的 10bit YUV (P010) 格式為例,介紹一下 10bit YUV 到 8bit YUV 的轉(zhuǎn)換過程删铃。

P010 最早是微軟定義的格式耳贬,表示的是 YUV 4:2:0 的采樣方式,也就是說 P010 表示的是一類 YUV 格式猎唁,它的內(nèi)存排布方式可能是 NV21咒劲、NV12、YU12诫隅、YV12 腐魂。

微軟定義的其他 10bit 和 16bit YUV 格式:


微軟定義的其他 10bit 和 16bit YUV 格式

下面我們討論的 P010 格式的內(nèi)存排布方式跟 NV21 格式一致,只是每個 Y逐纬、U蛔屹、V 分量分別占用 2 個字節(jié), 10bit 有效位豁生。

(0  ~  3) Y00  Y01  Y02  Y03  
(4  ~  7) Y10  Y11  Y12  Y13  
(8  ~ 11) Y20  Y21  Y22  Y23  
(12 ~ 15) Y30  Y31  Y32  Y33  

(16 ~ 19) V00  U00  V01  U01 
(20 ~ 23) V10  U10  V11  U11

P010 到 8bit YUV 轉(zhuǎn)換

根據(jù)上述 10bit YUV 的結(jié)構(gòu)圖判导,P010 轉(zhuǎn)換為 8bit YUV 可以通過向右移位(移 8 位)實現(xiàn)嫉父,而 8bit YUV 可以向左移 8 位,剛好低 6 位都是填 0 眼刃。

圖像定義:

struct NativeImage
{
    int width;
    int height;
    int format;
    uint8_t *ppPlane[3];
};

P010 轉(zhuǎn)換為 8bit YUV(NV21):

static int ConvertP010toNV21(NativeImage* pP010Img, NativeImage* pNV21Img) {
    if(pP010Img == nullptr
    || pNV21Img == nullptr
    || pP010Img->format != IMAGE_FORMAT_P010
    || pNV21Img->format != IMAGE_FORMAT_NV21) return -1;

    int width = pP010Img->width, height = pP010Img->height;
    for (int i = 0; i < height; ++i) {
        uint16_t *pu16YData = (uint16_t *)(pP010Img->ppPlane[0] + pP010Img->width * 2 * i);//每一行的起始位置
        uint8_t  *pu8YData = pNV21Img->ppPlane[0] + pNV21Img->width * i;
        for (int j = 0; j < width; j++, pu8YData++, pu16YData++) {
            *pu8YData = (u_int8_t)(*pu16YData >> 8);   //Y 分量向右移位(移 8 位)
        }
    }

    width /= 2; height /= 2;
    for (int i = 0; i < height; ++i) {
        uint16_t *pu16UVData = (uint16_t *)(pP010Img->ppPlane[1] + pP010Img->width * 2 * i);//每一行的起始位置
        uint8_t  *pu8UVData = pNV21Img->ppPlane[1] + pNV21Img->width * i;
        for (int j = 0; j < width; ++j, pu8UVData+=2, pu16UVData+=2) {
            *pu8UVData = *pu16UVData >> 8;             //V 分量向右移位(移 8 位)
            *(pu8UVData + 1) = *(pu16UVData + 1) >> 8; //U 分量向右移位(移 8 位)
        }
    }
    return 0;
}

8bit YUV(NV21)轉(zhuǎn)換為 P010 :

static int ConvertNV21toP010(NativeImage* pNV21Img, NativeImage* pP010Img) {
    if(pP010Img == nullptr
       || pNV21Img == nullptr
       || pP010Img->format != IMAGE_FORMAT_P010
       || pNV21Img->format != IMAGE_FORMAT_NV21) return -1;

    int width = pP010Img->width, height = pP010Img->height;
    for (int i = 0; i < height; ++i) {
        uint16_t *pu16YData = (uint16_t *)(pP010Img->ppPlane[0] + pP010Img->width * 2 * i);//每一行的起始位置
        uint8_t  *pu8YData = pNV21Img->ppPlane[0] + pNV21Img->width * i;
        for (int j = 0; j < width; j++, pu8YData++, pu16YData++) {
            *pu16YData = (u_int16_t)*pu8YData << 8;//Y 分量向左移位(移 8 位)
        }
    }

    width /= 2; height /= 2;
    for (int i = 0; i < height; ++i) {
        uint16_t *pu16UVData = (uint16_t *)(pP010Img->ppPlane[1] + pP010Img->width * 2 * i);//每一行的起始位置
        uint8_t  *pu8UVData = pNV21Img->ppPlane[1] + pNV21Img->width * i;
        for (int j = 0; j < width; ++j, pu8UVData+=2, pu16UVData+=2) {
            *pu16UVData = (u_int16_t)*pu8UVData << 8;   //V 分量向左移位(移 8 位)
            *(pu16UVData + 1) = (u_int16_t)*(pu8UVData + 1) << 8; //U 分量向左移位(移 8 位)
        }
    }

    return 0;
}

關(guān)于 P010 和 NV21 之間格式轉(zhuǎn)換測試绕辖,可以參考項目 https://github.com/githubhaohao/NDK_OpenGLES_3_0 , sample/YUVP010Example.h 源碼擂红。

class YUVP010Example {
public:
    static void YUVP010Test() {
        NativeImage p010Img, nv21Img;
        p010Img.width = 4406;
        p010Img.height = 3108;
        p010Img.format = IMAGE_FORMAT_P010;

        nv21Img = p010Img;
        nv21Img.format = IMAGE_FORMAT_NV21;

        //申請內(nèi)存
        NativeImageUtil::AllocNativeImage(&p010Img);
        NativeImageUtil::AllocNativeImage(&nv21Img);

        //加載 NV21 圖片
        char filePath[512] = {0};
        sprintf(filePath, "%s/yuv/%s", DEFAULT_OGL_ASSETS_DIR, DEFAULT_YUV_IMAGE_NAME);
        NativeImageUtil::LoadNativeImage(&nv21Img, filePath);

        //NV21 轉(zhuǎn)換為 P010
        {
            BEGIN_TIME("NativeImageUtil::ConvertNV21toP010")
            NativeImageUtil::ConvertNV21toP010(&nv21Img, &p010Img);
            END_TIME("NativeImageUtil::ConvertNV21toP010")
        }

        //保存 P010 圖像到手機
        NativeImageUtil::DumpNativeImage(&p010Img, DEFAULT_OGL_ASSETS_DIR, "IMAGE_P010");

        //P010 轉(zhuǎn)換為 NV21
        {
            BEGIN_TIME("NativeImageUtil::ConvertP010toNV21")
            NativeImageUtil::ConvertP010toNV21(&p010Img, &nv21Img);
            END_TIME("NativeImageUtil::ConvertP010toNV21")
        }

        //多線程實現(xiàn) P010 轉(zhuǎn)換為 NV21
        {
            BEGIN_TIME("NativeImageUtil::ConvertP010toNV21 MultiThread")
            std::thread *pThreads[3] = {nullptr};
            pThreads[0] = new std::thread(NativeImageUtil::ConvertP010PlaneTo8Bit, (u_int16_t*)p010Img.ppPlane[0], nv21Img.ppPlane[0], nv21Img.width, nv21Img.height / 2);
            pThreads[1] = new std::thread(NativeImageUtil::ConvertP010PlaneTo8Bit, (u_int16_t*)p010Img.ppPlane[0] + p010Img.height * p010Img.width / 2, nv21Img.ppPlane[0] + nv21Img.height * nv21Img.width / 2, nv21Img.width, nv21Img.height / 2);
            pThreads[2] = new std::thread(NativeImageUtil::ConvertP010PlaneTo8Bit, (u_int16_t*)p010Img.ppPlane[1], nv21Img.ppPlane[1], nv21Img.width, nv21Img.height / 2);
            for (int i = 0; i < 3; ++i) {
                pThreads[i]->join();
            }

            for (int i = 0; i < 3; ++i) {
                delete pThreads[i];
            }
            END_TIME("NativeImageUtil::ConvertP010toNV21 MultiThread")

        }

        NativeImageUtil::DumpNativeImage(&nv21Img, DEFAULT_OGL_ASSETS_DIR, "IMAGE_NV21");

        //釋放內(nèi)存
        NativeImageUtil::FreeNativeImage(&p010Img);
        NativeImageUtil::FreeNativeImage(&nv21Img);
    }
};

代碼中通過多線程實現(xiàn)格式轉(zhuǎn)換仪际,并與單線程轉(zhuǎn)換的性能進行對比,多線程轉(zhuǎn)換性能提升明顯:

性能進行

參考:
https://docs.microsoft.com/en-us/windows/win32/medfound/10-bit-and-16-bit-yuv-video-formats

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昵骤,一起剝皮案震驚了整個濱河市树碱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌变秦,老刑警劉巖成榜,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹦玫,居然都是意外死亡赎婚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門樱溉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挣输,“玉大人,你說我怎么就攤上這事福贞×媒溃” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵挖帘,是天一觀的道長完丽。 經(jīng)常有香客問我,道長拇舀,這世上最難降的妖魔是什么舰涌? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮你稚,結(jié)果婚禮上瓷耙,老公的妹妹穿的比我還像新娘。我一直安慰自己刁赖,他們只是感情好搁痛,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宇弛,像睡著了一般鸡典。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枪芒,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天彻况,我揣著相機與錄音谁尸,去河邊找鬼。 笑死纽甘,一個胖子當著我的面吹牛俩滥,可吹牛的內(nèi)容都是我干的尖昏。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掷倔!你這毒婦竟也來了胚委?” 一聲冷哼從身側(cè)響起拘领,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤褐墅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赏迟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屡贺,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年锌杀,在試婚紗的時候發(fā)現(xiàn)自己被綠了甩栈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡抛丽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饰豺,到底是詐尸還是另有隱情亿鲜,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布冤吨,位于F島的核電站蒿柳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漩蟆。R本人自食惡果不足惜垒探,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怠李。 院中可真熱鬧圾叼,春花似錦、人聲如沸捺癞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓介。三九已至惕鼓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唐础,已是汗流浹背箱歧。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工矾飞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呀邢。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓洒沦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驼鹅。 傳聞我的和親對象是個殘疾皇子微谓,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容