Android OpenGL渲染雙視頻

前言

想做的有很多,奈何能力實(shí)在有限削葱,所以只能一步一步來咆繁,將自己做出來的盡量用簡單易懂的語言描述出來,希望自己總結(jié)的對(duì)閱讀這篇文章的同學(xué)有所幫助艾船。

在上一篇文章 Android OpenGL使用GLSurfaceView預(yù)覽視頻中講述了怎樣在GLSurfaceView上預(yù)覽Camera的視頻數(shù)據(jù)葵腹,在本章中打算實(shí)現(xiàn)一個(gè)類似微信視頻通話的效果,微信視頻通話主要有大小兩個(gè)視頻數(shù)據(jù)渲染(自己的視頻和對(duì)端的視頻)屿岂,手指點(diǎn)擊小視頻践宴,可以切換大視頻和小視頻的位置,可以拖動(dòng)小視頻爷怀。

第一章 渲染多個(gè)視頻流數(shù)據(jù)

第一次看到這個(gè)功能阻肩,大部分人的第一個(gè)解決方案,就是創(chuàng)建多個(gè)View,每個(gè)View渲染一條視頻數(shù)據(jù)烤惊,這樣是可行的乔煞,但是如果是多人視頻呢?20個(gè)人就需要?jiǎng)?chuàng)建20個(gè)GLSurfaceView柒室,這樣顯然是不可行的渡贾,所以最好的辦法就是將所有的數(shù)據(jù)流都繪制在同一個(gè)GLSurfaceView上,這樣只需要控制OpenGL來控制視頻繪制的大小的位置就可以解決雄右,這樣很大程度上節(jié)省了內(nèi)存空骚,提升了效率。

首先看一下實(shí)際的效果圖:

<center>
[圖片上傳失敗...(image-4bb44a-1547534658224)]
</center>
<center>
[圖片上傳失敗...(image-788472-1547534658224)]
</center>

可以看到小視頻是疊加在大視頻上面的擂仍,雖然看上去好像是兩個(gè)view囤屹,但是其實(shí)所有的圖像都是繪制在同一個(gè)GLSurfaceView上的,我們需要做的就是計(jì)算出每個(gè)小圖縮放的比例逢渔,然后計(jì)算出每個(gè)小圖擺放的位置牺丙,視頻OpengGL的一些方法將視頻渲染的位置繪制到相應(yīng)的位置上。

首先复局,在函數(shù)onDrawFrame中繪制出所需要繪制的視頻數(shù)據(jù)冲簿。

 @Override
    public void onDrawFrame(GL10 gl) {
        // TODO Auto-generated method stub
        LOG.logI("onDrawFrame...");
        // 設(shè)置白色為清屏
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        // 清除屏幕和深度緩存
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        // 更新紋理
        mSurface.updateTexImage();

        // mDirectDrawers中有兩個(gè)對(duì)象,一個(gè)是繪制Camera傳遞過來的數(shù)據(jù)亿昏,一個(gè)是繪制由bitmap轉(zhuǎn)換成的紋理
        for (int i = 0; i < mDirectDrawers.size(); i++) {
            DirectDrawer directDrawer = mDirectDrawers.get(i);
            if (i == 0) {
                directDrawer.resetMatrix();
            } else {
                directDrawer.calculateMatrix(mThumbnailRect, mScreenWidth, mScreenHeight);
            }
            directDrawer.draw();
        }
    }

從上面的代碼可以看出峦剔,directDrawer.draw()調(diào)用了兩次,也就是說OpenGL在這個(gè)GLSurfaceView上繪制了兩次角钩,但是如果不做處理的話吝沫,第二個(gè)視頻渲染會(huì)覆蓋第一個(gè)效果。這里我們需要對(duì)第二個(gè)視頻流做一些處理:

  • 縮小:如下面的代碼所示递礼,我們將視頻轉(zhuǎn)換的矩陣存儲(chǔ)在一個(gè)16位的數(shù)組中惨险,即mMVP,我們需要在每次計(jì)算之前調(diào)用setIdentityM()脊髓,這行代碼的意思是將數(shù)據(jù)初始化到開始的位置和大小辫愉,因?yàn)槊看慰s小都是相對(duì)于初始的狀態(tài),接下來我們計(jì)算x軸和y軸的縮小比例将硝,這里我定義的是縮小1/4恭朗,然后調(diào)用scaleM就可以得到縮小后的比例,大概的過程如下圖所示:
        Matrix.setIdentityM(mMVP, 0);
        float scaleX = 1f / 4f;
        float scaleY = 1f / 4f;
        Matrix.scaleM(mMVP, 0, scaleX, scaleY, 0);

<center>
[圖片上傳失敗...(image-f8c7c7-1547534658224)]
</center>
可以從上圖中看到依疼,虛線為原來圖像的大小痰腮,經(jīng)過縮小后變?yōu)閷?shí)線矩形的大小

  • 移動(dòng):小視頻的初始化位置是左下方,所以需要將縮小后的視頻移動(dòng)到左下方律罢,代碼如下:
float ratioX = (rectF.left - .5f * (1 - scaleX) * screenWidth) / rectF.width();
float ratioY = (rectF.top - .5f * (1 + scaleY) * screenHeight) / rectF.height();
Matrix.translateM(mMVP, 0, ratioX * 2, ratioY * 2, 0f);

大致的過程如下圖:
<center>
[圖片上傳失敗...(image-2258d1-1547534658224)]
</center>

至此膀值,在同一個(gè)GLSurfaceView上繪制兩個(gè)視頻數(shù)據(jù),并且將第二個(gè)視頻縮小和移動(dòng)的過程就敘述完了,由于上面是將縮小和移動(dòng)分開來講沧踏,其實(shí)縮小和移動(dòng)的代碼是在一起的:

 public void calculateMatrix(RectF rectF, float screenWidth, float screenHeight) {
        Matrix.setIdentityM(mMVP, 0);
        float scaleX = 1f / 4f;
        float scaleY = 1f / 4f;
        float ratioX = (rectF.left - .5f * (1 - scaleX) * screenWidth) / rectF.width();
        float ratioY = (rectF.top - .5f * (1 + scaleY) * screenHeight) / rectF.height();
        Matrix.scaleM(mMVP, 0, scaleX, scaleY, 0);
        Matrix.translateM(mMVP, 0, ratioX * 2, ratioY * 2, 0f);
    }

第二章 滑動(dòng)視頻

移動(dòng)小視頻還是比較簡單的歌逢,上一章節(jié)已經(jīng)敘述了根據(jù)小視頻的位置(Rect),來對(duì)小視頻進(jìn)行縮小和移動(dòng)悦冀,所以我們只需要根據(jù)手機(jī)滑動(dòng)來改變小視頻的位置即可趋翻。下面給出移動(dòng)視頻的主要代碼睛琳。

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                if (mDownX > mThumbnailRect.left && mDownX < mThumbnailRect.right
                        && mDownY > mThumbnailRect.bottom && mDownY < mThumbnailRect.top) {
                    mTouchThumbnail = true;
                    mLastYLength = 0;
                    mLastXLength = 0;
                    return true;
                } else {
                    mTouchThumbnail = false;
                }

                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                if (mTouchThumbnail) {
                    float lengthX = Math.abs(mDownX - moveX);
                    float lengthY = Math.abs(mDownY - moveY);
                    float length = (float) Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2));
                    if (length > mTouchSlop) {
                        moveView(mThumbnailRect, mDownY - moveY, moveX - mDownX);
                        isMoveThumbnail = true;
                    } else {
                        isMoveThumbnail = false;
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTouchThumbnail) {
                    mLastYLength = 0;
                    mLastXLength = 0;
                    //抬起手指時(shí)盒蟆,如果不是移動(dòng)小視頻,那么就是點(diǎn)擊小視頻
                    if (!isMoveThumbnail) {
                        changeThumbnailPosition();
                    }
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }
  1. 判斷手指按下的位置是否在小視頻的區(qū)域中师骗,如果在历等,則記錄按下的X和Y的坐標(biāo)值,然后將mTouchThumbnail置為true辟癌。
  2. 如果手指移動(dòng)的距離超過Android定義的最小移動(dòng)距離寒屯,則開始改變小視頻的位置,否則判斷這次觸摸事件為點(diǎn)擊小視頻黍少。
  3. 根據(jù)X軸移動(dòng)的距離和Y軸移動(dòng)的距離改變小視頻的位置寡夹,然后在OpenGL繪制過程中移動(dòng)小視頻。

第三章 創(chuàng)建紋理

因?yàn)檫@里需要實(shí)現(xiàn)兩個(gè)視頻數(shù)據(jù)的渲染厂置,由于現(xiàn)在只能獲取攝像頭的數(shù)據(jù)菩掏,另一個(gè)為了更加直觀的顯示出效果,這里用一個(gè)bitmap的紋理來代替昵济,以后有了其他視頻的數(shù)據(jù)智绸,用相應(yīng)的紋理代替即可。

public static int loadTexture(Bitmap bitmap) {
        if (bitmap == null || bitmap.isRecycled()) {
            return 0;
        }

        int[] texture = new int[1];

        glGenTextures(1, texture, 0);

        if (texture[0] == 0) {
            return 0;
        }

        // Bind to the texture in OpenGL
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
        // Configure min/mag filtering, i.e. what scaling method do we use if what we're rendering
        // is smaller or larger than the source image.
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        // Load the bitmap into the bound texture.
        texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        return texture[0];
    }

glTexParameterf(int target, int pname, float param)函數(shù)用來確定如何把圖象從紋理圖象空間映射到幀緩沖圖象空間(如:映射時(shí)為了避免多邊形上的圖像失真访忿,而重新構(gòu)造紋理圖像等)瞧栗。

target

  • 目標(biāo)紋理(target),必須為GL_TEXTURE_1DGL_TEXTURE_2D或這是GL_TEXTURE_3D海铆;

pname

  • 過濾器(pname):GL_TEXTURE_MAG_FILTER(紋理放大時(shí)), GL_TEXTURE_MIN_FILTER(紋理縮小時(shí))
  • 環(huán)繞方向(pname):GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_R 分別為x迹恐,y,z方向卧斟。

param

  • pname為過濾器時(shí)的參數(shù):GL_NEARST(最鄰近的像素),GL_LINEAR(線性插值)
  • pname為環(huán)繞方向時(shí)的參數(shù):(以下系草,n為紋理方向上的紋理的長度)
    • GL_REPEAT:相當(dāng)于忽略掉紋理坐標(biāo)的整數(shù)部分。濾鏡為線性時(shí)唆涝,處于[1/2n找都,1]與第一個(gè)紋理像素融合。處于[0,1/2n]與最后一個(gè)像素融合廊酣。
    • GL_MIRRORED_REPEAT:相當(dāng)于將紋理坐標(biāo)1.1變成0.9能耻,達(dá)到鏡像反射的效果。
    • GL_CLAMP:截取紋理坐標(biāo)到 [0,1] 。將導(dǎo)致紋理坐標(biāo)處于[1-1/2n, 1]的像素晓猛,在紋理濾鏡為線性濾鏡時(shí)饿幅,與border融合,最終紋理坐標(biāo)為1的像素戒职,將為border和邊界像素的中值栗恩。
    • GL_CLAMP_TO_EDGE:截取紋理坐標(biāo)到[1/2n,1-1/2n]。將導(dǎo)致永遠(yuǎn)不會(huì)與border融合洪燥。
    • GL_CLAMP_TO_BORDER:截取紋理坐標(biāo)到[-1/2n,1+1/2n]磕秤。將導(dǎo)致紋理坐標(biāo)處于[1-1/2n,1+1/2n]范圍內(nèi)的像素,在紋理濾鏡為線性濾鏡時(shí)捧韵,與border融合市咆,最終紋理坐標(biāo)為1+1/2n的像素將于border同色。

總結(jié)

這篇文章講了實(shí)現(xiàn)類似微信視頻聊天的三個(gè)功能的大體實(shí)現(xiàn)思路再来,和一些基本的知識(shí)點(diǎn)的介紹蒙兰。下面我會(huì)給出源代碼的下載地址,感興趣的同學(xué)可以相互交流交流芒篷。

如果大家覺得還可以搜变,請(qǐng)給我點(diǎn)贊。

github下載代碼

CSDN下載代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末针炉,一起剝皮案震驚了整個(gè)濱河市挠他,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糊识,老刑警劉巖绩社,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赂苗,居然都是意外死亡愉耙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門拌滋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朴沿,“玉大人,你說我怎么就攤上這事败砂《脑” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵昌犹,是天一觀的道長坚芜。 經(jīng)常有香客問我,道長斜姥,這世上最難降的妖魔是什么鸿竖? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任沧竟,我火速辦了婚禮,結(jié)果婚禮上缚忧,老公的妹妹穿的比我還像新娘悟泵。我一直安慰自己,他們只是感情好闪水,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布糕非。 她就那樣靜靜地躺著,像睡著了一般球榆。 火紅的嫁衣襯著肌膚如雪朽肥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天芜果,我揣著相機(jī)與錄音鞠呈,去河邊找鬼融师。 笑死右钾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旱爆。 我是一名探鬼主播舀射,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼怀伦!你這毒婦竟也來了脆烟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤房待,失蹤者是張志新(化名)和其女友劉穎邢羔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桑孩,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拜鹤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了流椒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敏簿。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宣虾,靈堂內(nèi)的尸體忽然破棺而出惯裕,到底是詐尸還是另有隱情,我是刑警寧澤绣硝,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布蜻势,位于F島的核電站,受9級(jí)特大地震影響鹉胖,放射性物質(zhì)發(fā)生泄漏握玛。R本人自食惡果不足惜猜煮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望败许。 院中可真熱鬧王带,春花似錦、人聲如沸市殷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醋寝。三九已至搞挣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間音羞,已是汗流浹背囱桨。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗅绰,地道東北人舍肠。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像窘面,于是被迫代替她去往敵國和親翠语。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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