OpenGL.Shader:志哥教你写一个滤镜直播客户端(4)滤镜特效无缝切换设计

OpenGL.Shader:志哥教你写一个滤镜直播客户端(4)

上一章利用GpuBaseFilter.hpp完美地渲染了NV21流,这一章介绍如何设计多滤镜特效在视频流中无缝切换。

首先来详细回顾一下GpuBaseFilter.hpp:


#ifndef GPU_NORMAL_FILTER_HPP
#define GPU_NORMAL_FILTER_HPP

#include <string>
#include "../../program/ShaderHelper.h"
// 和 java/org.zzrblog.gpufilter.FilterType对应
// 而且对应的衍生类的getTypeId也要返回正确的值
#define FILTER_TYPE_NORMAL          0x1010
#define FILTER_TYPE_CONTRAST        0x1011
#define FILTER_TYPE_COLOR_INVERT    0x1012
#define FILTER_TYPE_PIXELATION      0x1013
// ... ...
/**
 * Filter基础类,支持YUV / RGB渲染模式。
 */
class GpuBaseFilter  {
public:
    // 用于上层获取滤镜列表对应的Filter类型
    virtual int getTypeId() { return FILTER_TYPE_NORMAL; }

    GpuBaseFilter()
    {  
        //初始化NO_FILTER_VERTEX_SHADER 和 NO_FILTER_FRAGMENT_SHADER
    }
    virtual ~GpuBaseFilter()
    {
        if(!NO_FILTER_VERTEX_SHADER.empty()) NO_FILTER_VERTEX_SHADER.clear();
        if(!NO_FILTER_FRAGMENT_SHADER.empty()) NO_FILTER_FRAGMENT_SHADER.clear();
        mIsInitialized = false;
    }

    virtual void init() {
        init(NO_FILTER_VERTEX_SHADER.c_str(), NO_FILTER_FRAGMENT_SHADER.c_str());
    }

    void init(const char *vertexShaderSource, const char *fragmentShaderSource) {
        mGLProgId = ShaderHelper::buildProgram(vertexShaderSource, fragmentShaderSource);
        ... ... 
        mIsInitialized = true;
    }

    virtual void destroy() {
        mIsInitialized = false;
        glDeleteProgram(mGLProgId);
    }

    virtual void onOutputSizeChanged(int width, int height) {
        mOutputWidth = width;
        mOutputHeight = height;
    }

    virtual void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        ... ...
    }

    // 相关滤镜对应的可调整参数,通过此借口进行操作
    virtual void setAdjustEffect(float percent) {
        // subclass override
    }

    bool isInitialized(){ return mIsInitialized;}
    GLuint getProgram(){ return mGLProgId;}
protected:
    std::string NO_FILTER_VERTEX_SHADER;
    std::string NO_FILTER_FRAGMENT_SHADER;

    GLuint  mGLProgId;
    GLuint  mGLAttribPosition;
    GLuint  mGLUniformSampleRGB;
    GLuint  mGLAttribTextureCoordinate;

    GLuint  mGLUniformSampleY;
    GLuint  mGLUniformSampleU;
    GLuint  mGLUniformSampleV;

    int     mOutputWidth;
    int     mOutputHeight;
    bool    mIsInitialized;
};

#endif // GPU_NORMAL_FILTER_HPP

新增了四个宏定义,附带一句注释。显然这些是拟定各种特效滤镜的类型身份id。 

// 和 java/org.zzrblog.gpufilter.FilterType对应
// 而且对应的衍生类的getTypeId也要返回正确的值
#define FILTER_TYPE_NORMAL 0x1010
#define FILTER_TYPE_CONTRAST 0x1011
#define FILTER_TYPE_COLOR_INVERT 0x1012
#define FILTER_TYPE_PIXELATION 0x1013

还要注意其相中几个带virtual关键字的方法,这些是需要继承类重写的方法。

接着以对比度滤镜FILTER_TYPE_CONTRAST为例,看看GpuBaseFilter的衍生类——GpuContrastFilter

#ifndef GPU_CONTRAST_FILTER_HPP
#define GPU_CONTRAST_FILTER_HPP
#include "GpuBaseFilter.hpp"
/**
 * 更改图像的对比度。
 * 对比度值在0.0到4.0之间,正常值为1.0
 */
class GpuContrastFilter : public GpuBaseFilter {
public:
    int getTypeId() { return FILTER_TYPE_CONTRAST; }

    GpuContrastFilter()
    {
        CONTRAST_FRAGMENT_SHADER  ="precision mediump float;\n\
                                    varying highp vec2 textureCoordinate;\n\
                                    uniform sampler2D SamplerRGB;\n\
                                    uniform sampler2D SamplerY;\n\
                                    uniform sampler2D SamplerU;\n\
                                    uniform sampler2D SamplerV;\n\
                                    uniform lowp float contrast;\n\
                                    mat3 colorConversionMatrix = mat3(\n\
                                                       1.0, 1.0, 1.0,\n\
                                                       0.0, -0.39465, 2.03211,\n\
                                                       1.13983, -0.58060, 0.0);\n\
                                    vec3 yuv2rgb(vec2 pos)\n\
                                    {\n\
                                       vec3 yuv;\n\
                                       yuv.x = texture2D(SamplerY, pos).r;\n\
                                       yuv.y = texture2D(SamplerU, pos).r - 0.5;\n\
                                       yuv.z = texture2D(SamplerV, pos).r - 0.5;\n\
                                       return colorConversionMatrix * yuv;\n\
                                    }\n\
                                    void main()\n\
                                    {\n\
                                       vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
                                       gl_FragColor = vec4((contrast*(textureColor.rgb - vec3(0.5)) + vec3(0.5)), textureColor.w);\n\
                                    }";
    }
    ~GpuContrastFilter() {
        if(!CONTRAST_FRAGMENT_SHADER.empty()) CONTRAST_FRAGMENT_SHADER.clear();
    }

    void init() {
        GpuBaseFilter::init(NO_FILTER_VERTEX_SHADER.c_str(), CONTRAST_FRAGMENT_SHADER.c_str());
        mContrastLocation = glGetUniformLocation(mGLProgId, "contrast");
        mContrastValue = 1.0f;
    }

    void setAdjustEffect(float percent) {
        mContrastValue = percent * 4.0f;
    }

    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                        void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(mGLProgId);
        glUniform1f(mContrastLocation, mContrastValue);

        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
        glUniform1i(mGLUniformSampleY, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
        glUniform1i(mGLUniformSampleU, 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
        glUniform1i(mGLUniformSampleV, 2);

        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
private:
    std::string CONTRAST_FRAGMENT_SHADER;

    GLint   mContrastLocation;
    float   mContrastValue;
};
#endif //GPU_CONTRAST_FILTER_HPP

代码内容不再这里论述,往后会以滤镜为系列详细分析。以下几个细节点顺带一提:

1、GpuBaseFilter的继承类,重写getTypeId方法要返回正确的类型值。
2、构造函数会自动构造父类,所以父类GpuBaseFilter的protected字段NO_FILTER_VERTEX_SHADER和NO_FILTER_FRAGMENT_SHADER是可以按需直接使用,不要重写顶点着色器。
3、无参方法init()是对外使用的,带参数的init是重载,供集成类内部编译Shader调用。
4、setAdjustEffect动态调节效果使用,按需重写;其他没有重写的方法沿用基类原函数。

其余衍生的滤镜类的内容都差不多,以后再详细分析。回归到滤镜无缝切换的设计上,现在回到java应用层分析,CFEScheduler.java部分代码如下:

public class FilterType {
    // NOTE:这里的ID值要 与
    // cpp/gpufilter/filter/Gpu*****Filter.hpp的getTypeId一一对应
    static final int FILTER_TYPE_NORMAL = 0x1010;
    static final int FILTER_TYPE_CONTRAST = 0x1011;
    static final int FILTER_TYPE_COLOR_INVERT = 0x1012;
    static final int FILTER_TYPE_PIXELATION = 0x1013;

    public List<String> names = new LinkedList<String>();
    public List<Integer> filters = new LinkedList<Integer>();

    public void addFilter(final String name, final int typeId) {
        names.add(name);
        filters.add(typeId);
    }
}

public class CFEScheduler {
    /*Filter相关*/
    public void setFilterType(int typeId) {
        if(mGpuFilterRender!=null)
            mGpuFilterRender.setFilterType(typeId);
    }
    private FilterType supportFilters;
    public String[] getSupportedFiltersName() {
        if( supportFilters ==null ) {
            supportFilters = new FilterType();
            supportFilters.addFilter("Normal", FilterType.FILTER_TYPE_NORMAL);
            supportFilters.addFilter("Contrast", FilterType.FILTER_TYPE_CONTRAST);
            // ...
        }
        return supportFilters.names.toArray(new String[supportFilters.names.size()]);
    }
    public int getSupportedFilterTypeID(String name) {
        if(supportFilters!=null) {
            try{// 防止空指针|越界|查询失败
                int position = supportFilters.names.indexOf(name);
                return supportFilters.filters.get(position);
            }catch (Exception e){
                return 0;
            }
        }
        return 0;
    }
    public int getSupportedFilterTypeID(int index) {
        if(supportFilters!=null) {
            try{// 防止空指针|越界|查询失败
                return supportFilters.filters.get(index);
            }catch (Exception e){
                return 0;
            }
        }
        return 0;
    }
    public void adjustFilterValue(int value, int max) {
        if(mGpuFilterRender!=null)
            mGpuFilterRender.adjustFilterValue(value, max);
    }
    // ...
}

1、getSupportedFiltersName()方法供外部调用,用于提供给Spinner等选择控件显示名称使用。
2、getSupportedFilterTypeID两个重载方法,用于通过查询具体的FilterTypeID。
3、setFilterType方法根据传入的FilterTypeID通知GpuFilterRender切换滤镜。
4、adjustFilterValue方法用于动态调整当前滤镜的效果,value/max取百分比值。

以上方法setFilterType滤镜切换最为密切,跟踪进入GpuFilterRender.cpp。

void GpuFilterRender::setFilter(int filter_type_id) {
    mRequestTypeId = filter_type_id;
}

继续跟踪可以发现GpuFilterRender.h包含两个相关变量mRequestTypeId / mCurrentTypeId,显然mRequestTypeId是代表申请切换新的滤镜Id,mCurrentTypeId是代表当前的滤镜Id。追加方法checkFilterChange,看看两者会怎样相处。

void GpuFilterRender::checkFilterChange() {
    if(mCurrentTypeId!=mRequestTypeId) {
        // 更新filter
        if( mFilter!=NULL) {
            mFilter->destroy();
            delete mFilter;
            mFilter = NULL;
        }
        switch (mRequestTypeId)
        {
            case FILTER_TYPE_CONTRAST:{
                mFilter = new GpuContrastFilter();
            }break;
            default:
                mFilter = new GpuBaseFilter();
                break;
        }
        if( mFilter!=NULL) {
            mFilter->init();
            mFilter->onOutputSizeChanged(mViewWidth, mViewHeight);
            mCurrentTypeId = mRequestTypeId;
        }
    }
}

代码内容不难理解,但是我要提醒一个注意点,init()和destroy()方法是要执行GL语句的,所以要运行在GLThread上,显然执行checkFilterChange的最佳地方是renderOnDraw。

void GpuFilterRender::renderOnDraw(double elpasedInMilliSec)
{
        // 画面渲染
        mWindowSurface->makeCurrent();
        yTextureId = updateTexture(dst_y, yTextureId, mFrameWidth, mFrameHeight);
        uTextureId = updateTexture(dst_u, uTextureId, mFrameWidth/2, mFrameHeight/2);
        vTextureId = updateTexture(dst_v, vTextureId, mFrameWidth/2, mFrameHeight/2);
        // 检测更新Filter
        checkFilterChange();
        if( mFilter!=NULL) {
            mFilter->setAdjustEffect(mFilterEffectPercent);
            mFilter->onDraw(yTextureId, uTextureId, vTextureId, positionCords, textureCords);
        }
        // ...    
}

赶紧试下效果?!

项目地址:https://github.com/MrZhaozhirong/NativeCppApp

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/104478360