MediaCodec 解碼后數(shù)據(jù)對齊導(dǎo)致的綠邊問題

前言

Android 使用 MediaCodec 解碼 h264 數(shù)據(jù)后會有個數(shù)據(jù)對齊的問題。

簡單說就是 MediaCodec 使用 GPU 進行解碼,而解碼后的輸出數(shù)據(jù)是有一個對齊規(guī)則的外驱,不同設(shè)備表現(xiàn)不一,如寬高都是 16 位對齊援奢,或 32 位驻债、64 位挚躯、128 位强衡,當(dāng)然也可能出現(xiàn)類似寬度以 128 位對齊而高度是 32 位對齊的情況。

例子

簡單起見先畫個 16 位對齊的:

假設(shè)需要解碼的圖像寬高為 15*15码荔,在使用 16 位對齊的設(shè)備進行硬解碼后漩勤,輸出的 YUV 數(shù)據(jù)將會是 16*16 的感挥,而多出來的寬高將自動填充。這時候如果按照 15*15 的大小取出 YUV 數(shù)據(jù)進行渲染越败,表現(xiàn)為花屏触幼,而按照 16*16 的方式渲染,則出現(xiàn)綠邊(如上圖)究飞。

怎么去除綠邊呢置谦?很簡單,把原始圖像摳出來就行了(廢話)亿傅。

以上面為例子媒峡,分別取出 YUV 數(shù)據(jù)的話,可以這么做:

int width = 15, height = 15;
int alignWidth = 16, alignHeight = 16;

//假設(shè) outData 是解碼后對齊數(shù)據(jù)
byte[] outData = new byte[alignWidth * alignHeight * 3 / 2];

byte[] yData = new byte[width * height];
byte[] uData = new byte[width * height / 4];
byte[] vData = new byte[width * height / 4];

yuvCopy(outData, 0, alignWidth, alignHeight, yData, width, height);
yuvCopy(outData, alignWidth * alignHeight, alignWidth / 2, alignHeight / 2, uData, width / 2, height / 2);
yuvCopy(outData, alignWidth * alignHeight * 5 / 4, alignWidth / 2, alignHeight / 2, vData, width / 2, height / 2);

...

private static void yuvCopy(byte[] src, int offset, int inWidth, int inHeight, byte[] dest, int outWidth, int outHeight) {
    for (int h = 0; h < inHeight; h++) {
        if (h < outHeight) {
            System.arraycopy(src, offset + h * inWidth, dest, h * outWidth, outWidth);
        }
    }
}

其實就是逐行摳出有效數(shù)據(jù)啦~

問題

那現(xiàn)在的問題就剩怎么知道解碼后輸出數(shù)據(jù)的寬高了葵擎。

起初我用華為榮耀note8做測試機谅阿,解碼 1520*1520 后直接按照 1520*1520 的方式渲染是沒問題的,包括解碼后給的 buffer 大小也是 3465600(也就是 1520*1520*3/2)酬滤。

而當(dāng)我使用OPPO R11签餐,解碼后的 buffer 大小則為 3538944(1536*1536*3/2),這時候再按照 1520*1520 的方式渲染的話盯串,圖像是這樣的:

花啦啦

使用 yuvplayer 查看數(shù)據(jù)最終確定 1536*1536 方式渲染是沒問題的氯檐,那么 1536 這個值在代碼中怎么得到的呢?

我們可以拿到解碼后的 buffer 大小嘴脾,同時也知道寬高的對齊無非就是 16男摧、32、64译打、128 這幾個值,那很簡單了拇颅,根據(jù)原來的寬高做對齊一個個找奏司,如下(不著急,后面還有坑樟插,這里先給出第一版解決方案):

align:
for (int w = 16; w <= 128; w = w << 1) {
    for (int h = 16; h <= w; h = h << 1) {
        alignWidth = ((width - 1) / w + 1) * w;
        alignHeight = ((height - 1) / h + 1) * h;
        int size = alignWidth * alignHeight * 3 / 2;
        if (size == bufferSize) {
            break align;
        }
    }
}

代碼比較簡單韵洋,大概就是從 16 位對齊開始一個個嘗試,最終得到跟 bufferSize 相匹配的寬高黄锤。

當(dāng)我屁顛屁顛的把 apk 發(fā)給老大之后搪缨,現(xiàn)實又無情地甩了我一巴掌,還好我在自己新買的手機上面調(diào)試了一下啊哈哈哈哈哈~

你以為華為的機子表現(xiàn)都是一樣的嗎鸵熟?錯了副编,我的華為mate9就不是醬紫的,它解出來的 buffer 大小是 3538944(1536*1536*3/2)流强,而當(dāng)我按照上面的方法得到 1536 這個值之后痹届,渲染出來的圖像跟上面的花屏差不多呻待,誰能想到他按照 1520*1520 的方式渲染才是正常的。

這里得到結(jié)論:通過解碼后 buffer 的 size 來確定對齊寬高的方法是不可靠的队腐。

解決方案

就在我快絕望的時候蚕捉,我在官方文檔上發(fā)現(xiàn)這個(網(wǎng)上資料太少了,事實證明官方文檔的資料才最可靠):

Accessing Raw Video ByteBuffers on Older Devices

Prior to LOLLIPOP and Image support, you need to use the KEY_STRIDE and KEY_SLICE_HEIGHT output format values to understand the layout of the raw output buffers.

Note that on some devices the slice-height is advertised as 0. This could mean either that the slice-height is the same as the frame height, or that the slice-height is the frame height aligned to some value (usually a power of 2). Unfortunately, there is no standard and simple way to tell the actual slice height in this case. Furthermore, the vertical stride of the U plane in planar formats is also not specified or defined, though usually it is half of the slice height.

大致就是使用 KEY_STRIDEKEY_SLICE_HEIGHT 可以得到原始輸出 buffer 的對齊后的寬高柴淘,但在某些設(shè)備上可能會獲得 0迫淹,這種情況下要么它跟圖像的值相等,要么就是對齊后的某值为严。

OK千绪,那么當(dāng) KEY_STRIDEKEY_SLICE_HEIGHT 能拿到數(shù)據(jù)的時候我們使用他們,拿不到的時候再用第一個解決方案:

//視頻寬高梗脾,如果存在裁剪范圍的話荸型,寬等于右邊減左邊坐標(biāo),高等于底部減頂部
width = format.getInteger(MediaFormat.KEY_WIDTH);
if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
    width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
}
height = format.getInteger(MediaFormat.KEY_HEIGHT);
if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
    height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
}

//解碼后數(shù)據(jù)對齊的寬高炸茧,在有些設(shè)備上會返回0
int keyStride = format.getInteger(MediaFormat.KEY_STRIDE);
int keyStrideHeight = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT);
// 當(dāng)對齊后高度返回0的時候瑞妇,分兩種情況,如果對齊后寬度有給值梭冠,
// 則只需要計算高度從16字節(jié)對齊到128字節(jié)對齊這幾種情況下哪個值跟對齊后寬度相乘再乘3/2等于對齊后大小辕狰,
// 如果計算不出則默認等于視頻寬高。
// 當(dāng)對齊后寬度也返回0控漠,這時候也要對寬度做對齊處理蔓倍,原理同上
alignWidth = keyStride;
alignHeight = keyStrideHeight;
if (alignHeight == 0) {
    if (alignWidth == 0) {
        align:
        for (int w = 16; w <= 128; w = w << 1) {
            for (int h = 16; h <= w; h = h << 1) {
                alignWidth = ((videoWidth - 1) / w + 1) * w;
                alignHeight = ((videoHeight - 1) / h + 1) * h;
                int size = alignWidth * alignHeight * 3 / 2;
                if (size == bufferSize) {
                    break align;
                }
            }
        }
    } else {
        for (int h = 16; h <= 128; h = h << 1) {
            alignHeight = ((videoHeight - 1) / h + 1) * h;
            int size = alignWidth * alignHeight * 3 / 2;
            if (size == bufferSize) {
                break;
            }
        }
    }
    int size = alignWidth * alignHeight * 3 / 2;
    if (size != bufferSize) {
        alignWidth = videoWidth;
        alignHeight = videoHeight;
    }
}

int size = videoWidth * videoHeight * 3 / 2;
if (size == bufferSize) {
    alignWidth = videoWidth;
    alignHeight = videoHeight;
} 

最后說兩句

文中只提供了個人處理的思路,實際使用的時候盐捷,還要考慮顏色格式以及效率的問題偶翅,個人不建議在java代碼層面做這類轉(zhuǎn)換。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碉渡,一起剝皮案震驚了整個濱河市聚谁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滞诺,老刑警劉巖形导,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異习霹,居然都是意外死亡朵耕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門淋叶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阎曹,“玉大人,你說我怎么就攤上這事》蚁ィ” “怎么了望门?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長锰霜。 經(jīng)常有香客問我筹误,道長,這世上最難降的妖魔是什么癣缅? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任厨剪,我火速辦了婚禮,結(jié)果婚禮上友存,老公的妹妹穿的比我還像新娘祷膳。我一直安慰自己,他們只是感情好屡立,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布直晨。 她就那樣靜靜地躺著,像睡著了一般膨俐。 火紅的嫁衣襯著肌膚如雪勇皇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天焚刺,我揣著相機與錄音敛摘,去河邊找鬼。 笑死乳愉,一個胖子當(dāng)著我的面吹牛兄淫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔓姚,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼捕虽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赂乐?” 一聲冷哼從身側(cè)響起薯鳍,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挨措,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崩溪,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡浅役,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了伶唯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觉既。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞪讼,到底是詐尸還是另有隱情钧椰,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布符欠,位于F島的核電站嫡霞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏希柿。R本人自食惡果不足惜诊沪,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曾撤。 院中可真熱鬧端姚,春花似錦、人聲如沸挤悉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽装悲。三九已至昏鹃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衅斩,已是汗流浹背盆顾。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留畏梆,地道東北人您宪。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像奠涌,于是被迫代替她去往敵國和親宪巨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

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

  • 原文:https://developer.android.com/reference/android/media/...
    thebestofrocky閱讀 6,056評論 0 6
  • 本篇文章是基于谷歌有關(guān)Graphic的一篇概覽文章的翻譯:http://source.android.com/de...
    lee_3do閱讀 7,109評論 2 21
  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,683評論 0 3
  • HTC(高通) 編碼端:zhanganl encode frame.width():360 frame.heig...
    ai___believe閱讀 2,542評論 0 3
  • 前兩天看了一個新聞溜畅,一個小學(xué)生給市長寫信捏卓,說看了林志玲的內(nèi)衣廣告很沖動,希望不要再看到慈格。后來娛樂記者還把這個新聞告...
    繼續(xù)海闊天空閱讀 549評論 0 2