前言
給視頻加水印
public class WaterMarkFilter extends NoFilter{
}
然後在構造函數中創建一個NoFilter mFilter=new NoFilter(mRes){
@Override
protected void onClear() {
}
};
@Override
protected void onCreate() {
super.onCreate();
mFilter.create();
createTexture();
}
@Override
protected void onSizeChanged(int width, int height) {
this.width=width;
this.height=height;
mFilter.setSize(width,height);
}
@Override
public void draw() {
super.draw();
GLES20.glViewport(x,y,w == 0 ? mBitmap.getWidth():w,h==0?mBitmap.getHeight():h);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_DST_ALPHA);
mFilter.draw();
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glViewport(0,0,width,height);
}
public void setWaterMark(Bitmap bitmap){
if(this.mBitmap!=null){
this.mBitmap.recycle();
}
this.mBitmap=bitmap;
}
public void setPosition(int x,int y,int width,int height){
this.x=x;
this.y=y;
this.w=width;
this.h=height;
}
然後這個給將原始畫面和水印圖片進行混合的代碼就完成了 private void createTexture() {
if(mBitmap!=null){
//生成紋理
GLES20.glGenTextures(1,textures,0);
//生成紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textures[0]);
//設置縮小過濾爲使用紋理中座標最接近的一個像素的顏色作爲需要繪製的像素顏色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
//設置放大過濾爲使用紋理中座標最接近的若干個顏色,通過加權平均算法得到需要繪製的像素顏色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
//設置環繞方向S,截取紋理座標到[1/2n,1-1/2n]。將導致永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
//設置環繞方向T,截取紋理座標到[1/2n,1-1/2n]。將導致永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
//對畫面進行矩陣旋轉
MatrixUtils.flip(mFilter.getMatrix(),false,true);
mFilter.setTextureId(textures[0]);
}
}
其中非常關鍵的一行就是,GLUtils.textImage2D(GLES20.GL_TEXTURE_2D,0,mBitmap,0) 。GroupFilter類
private Queue<AFilter> mFilterQueue;
private List<AFilter> mFilters;
在構造函數中進行初始化 public GroupFilter(Resources res) {
super(res);
mFilters=new ArrayList<>();
mFilterQueue=new ConcurrentLinkedQueue<>();
}
提供添加Filter的方法 public void addFilter(final AFilter filter){
mFilterQueue.add(filter);
}
然後寫一個updateFilter,用於將Filter,從Queue中取出,加入到List中 private void updateFilter(){
AFilter f;
while ((f=mFilterQueue.poll())!=null){
f.create();
f.setSize(width,height);
mFilters.add(f);
size++;
}
}
然後創建離屏的buffer以及輸入和輸出的Texture private int fTextureSize = 2;
private int[] fFrame = new int[1];
private int[] fRender = new int[1];
private int[] fTexture = new int[fTextureSize];
//創建FrameBuffer
private boolean createFrameBuffer() {
GLES20.glGenFramebuffers(1, fFrame, 0);
GLES20.glGenRenderbuffers(1, fRender, 0);
genTextures();
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, fRender[0]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width,
height);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fTexture[0], 0);
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, fRender[0]);
// int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
// if(status==GLES20.GL_FRAMEBUFFER_COMPLETE){
// return true;
// }
unBindFrame();
return false;
}
//生成Textures
private void genTextures() {
GLES20.glGenTextures(fTextureSize, fTexture, 0);
for (int i = 0; i < fTextureSize; i++) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fTexture[i]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
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);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
}
}
然後就是draw的代碼,主要是從List中,取出Filter,並且進行繪製 public void draw(){
updateFilter();
textureIndex=0;
GLES20.glViewport(0,0,width,height);
for (AFilter filter:mFilters){
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fTexture[textureIndex%2], 0);
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, fRender[0]);
if(textureIndex==0){
filter.setTextureId(getTextureId());
}else{
filter.setTextureId(fTexture[(textureIndex-1)%2]);
}
filter.draw();
unBindFrame();
textureIndex++;
}
}
GroupFilter的核心代碼就是上面這些,然後我們需要將水印的Filter 在我們上文中的CameraDrawer中進行創建和添加,然後在合適的位置進行繪製。在CameraDrawer代碼中,基於上文中的代碼,創建一個GroupFilter的實例,
/**繪製水印的filter組*/
private final GroupFilter mBeFilter;
在構造函數中進行水印的Filter的創建和添加 mBeFilter = new GroupFilter(resources);
WaterMarkFilter waterMarkFilter = new WaterMarkFilter(resources);
waterMarkFilter.setWaterMark(BitmapFactory.decodeResource(resources,R.mipmap.watermark));
waterMarkFilter.setPosition(30,50,0,0);
mBeFilter.addFilter(waterMarkFilter);
然後在onSurfaceCreated方法中進行mBeFilter的初始化 mBeFilter.create();
在onSurfaceChanged方法中,設置GroupFilter的size drawFilter.setSize(mPreviewWidth,mPreviewHeight);
然後我們需要對核心的onDrawFrame方法進行改造,我們現在的繪製流程是這樣的,首先使用drawFilter將攝像頭的畫面,繪製到離屏的fFrame和fTexture中 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fTexture[0], 0);
GLES20.glViewport(0,0,mPreviewWidth,mPreviewHeight);
drawFilter.draw();
//解綁
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
下一步,我們需要調用GroupFilter,將繪製的畫面輸入,然後和水印進行混合,然後再輸出 mBeFilter.setTextureId(fTexture[0]);
mBeFilter.draw();
下一步就是,從GroupFilter取出繪製完成的texture,我們需要將這個texture分別給到兩個第一,第一個就是屏幕上顯示,第二個就是給到Encoder,進行後臺的視頻編碼。 /**繪製顯示的filter*/
GLES20.glViewport(0,0,width,height);
showFilter.setTextureId(mBeFilter.getOutputTexture());
showFilter.draw();
/**將加過水印的texture,給到encoder進行後臺的編碼*/
if (videoEncoder != null && recordingEnabled && recordingStatus == RECORDING_ON){
videoEncoder.setTextureId(mBeFilter.getOutputTexture());
videoEncoder.frameAvailable(mSurfaceTextrue);
}
通過上面的流程,我們就完成了視頻加水印在屏幕上顯示以及編碼到視頻中。
預覽和錄製加上美白效果
public MagicBeautyFilter(){
super(NO_FILTER_VERTEX_SHADER ,
OpenGlUtils.readShaderFromRawResource(R.raw.beauty));
}
這裏的R.raw.beauty文件,就是美白算法的實現shader。 public GPUImageFilter(final String vertexShader, final String fragmentShader) {
mRunOnDraw = new LinkedList<>();
mVertexShader = vertexShader;
mFragmentShader = fragmentShader;
mGLCubeBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.CUBE.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLCubeBuffer.put(TextureRotationUtil.CUBE).position(0);
mGLTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, true)).position(0);
}
包括了shader的設置以及一些buffer的初始化 protected void onInit() {
super.onInit();
mSingleStepOffsetLocation = GLES20.glGetUniformLocation(getProgram(), "singleStepOffset");
mParamsLocation = GLES20.glGetUniformLocation(getProgram(), "params");
setBeautyLevel(3);//beauty Level
}
然後就是對外提供設置美白的等級的方法 public void setBeautyLevel(int level){
mLevel=level;
switch (level) {
case 1:
setFloat(mParamsLocation, 1.0f);
break;
case 2:
setFloat(mParamsLocation, 0.8f);
break;
case 3:
setFloat(mParamsLocation,0.6f);
break;
case 4:
setFloat(mParamsLocation, 0.4f);
break;
case 5:
setFloat(mParamsLocation,0.33f);
break;
default:
break;
}
}
而這個setFloat方法,就是修改shader文件裏面的params的值 setFloat的代碼主要如下
protected void setFloat(final int location, final float floatValue) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glUniform1f(location, floatValue);
}
});
}
到這裏美白的filter就算寫完了。接下來我們就要在CamreaDrawer類中添加上美白filter的使用了。 mBeautyFilter = new MagicBeautyFilter();
然後在onSurfaceCreated中進行初始化 mBeautyFilter.init();
在onSurfaceChanged中進行size的設置 mBeautyFilter.onDisplaySizeChanged(mPreviewWidth,mPreviewHeight);
mBeautyFilter.onInputSizeChanged(mPreviewWidth,mPreviewHeight);
然後在onDrawFrame方法中,如果當前是需要美白的話,就對數據進行美白繪製 if (mBeautyFilter != null && mBeautyFilter.getBeautyLevel() != 0){
EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);
GLES20.glViewport(0,0,mPreviewWidth,mPreviewHeight);
mBeautyFilter.onDrawFrame(mBeFilter.getOutputTexture());
EasyGlUtils.unBindFrameBuffer();
mProcessFilter.setTextureId(fTexture[0]);
}else {
mProcessFilter.setTextureId(mBeFilter.getOutputTexture());
}
基本過程就是,如果是要進行美白的話,就將紋理傳遞到美白的filter中,而美白的filter的onDrawFrame函數主要做了什麼?這就涉及到了以後我們會說的給視頻加濾鏡之類的操作,我們在後面的文章中進行詳細的解釋。這裏暫時就不深入了。需要知道的是,經過上述代碼的操作,就成功的給視頻加上了美白效果。然後在CameraDrawer類中,對上層提供修改美白效果等級的接口
/**提供修改美白等級的接口*/
public void changeBeautyLevel(int level){
mBeautyFilter.setBeautyLevel(level);
}
public int getBeautyLevel(){
return mBeautyFilter.getBeautyLevel();
}
到這裏,我們的美白fitler以及將其在覈心的繪製類中使用,已經全部寫完了,現在要做的就是,給項目上層ui,加上一些控制美白效果的代碼。就不仔細說了
。