上一章利用GpuBaseFilter.hpp完美地渲染了NV21流,这一章介绍如何设计多滤镜特效在视频流中无缝切换。
首先来详细回顾一下GpuBaseFilter.hpp:
#ifndef GPU_NORMAL_FILTER_HPP
#define GPU_NORMAL_FILTER_HPP
#include
#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 names = new LinkedList();
public List filters = new LinkedList();
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