前言
想做的有很多,奈何能力實(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);
}
- 判斷手指按下的位置是否在小視頻的區(qū)域中师骗,如果在历等,則記錄按下的X和Y的坐標(biāo)值,然后將
mTouchThumbnail
置為true
辟癌。 - 如果手指移動(dòng)的距離超過Android定義的最小移動(dòng)距離寒屯,則開始改變小視頻的位置,否則判斷這次觸摸事件為點(diǎn)擊小視頻黍少。
- 根據(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_1D
或GL_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)贊。