OpenGL.Shader:Zhigeは、ライブフィルタークライアントの作成方法を教えています(3)
最後の章では2つのポイントについて説明しました。最初のポイントは、NV21フレームデータをキャッシュするフレームデータバッファプールです(コールスタックはJClassアクティビティ-> JClass CFEScheduler-> JCallback Camera.onPreviewFrame-> JNIMethod feedVideoDataです)。2番目はsetRotationCameraです。回転角度に応じて水平方向と垂直方向のフリップを調整します(呼び出しスタックはJClassアクティビティ-> JClass CFEScheduler-> JMethod setUpCamera-> JNIMethod setRotationCameraです) ;どちらも最終的にadjustFrameScalingをトリガーし、フレームイメージのスケーリングを調整し、OpenGLレンダリング用の頂点を生成します座標データとテクスチャ座標データ。
次のステップは、実際に画像データをスクリーンキャリアにレンダリングすることです。今回は、前編で紹介した透かし記録とは少し異なり、前編で紹介した方法は、システムが用意したテクスチャオブジェクトを使用する方法で、テクスチャIDを借用して直接操作します。しかし、今回は最も原始的なNV21形式の画像フレームデータしかないため、画像フレームデータを出力キャリアサーフェスに効率的にレンダリングする方法が重要な問題になっています。
もう1つ注意が必要です。さまざまなフィルター効果を追加した後、シームレスに切り替えるにはどうすればよいですか。(とりあえず、ここにポイントを入れてください)それ以上の苦労なしに、コードを示してください。
まず、GLThread + GLRenderの3つのライフサイクルコールバックの内容を確認します(質問がある場合は、前の記事https://blog.csdn.net/a360940265a/article/details/88600962を参照してください)。
void GpuFilterRender::surfaceCreated(ANativeWindow *window)
{
if (mEglCore == NULL) {
mEglCore = new EglCore(NULL, FLAG_TRY_GLES2);
}
mWindowSurface = new WindowSurface(mEglCore, window, true);
assert(mWindowSurface != NULL && mEglCore != NULL);
LOGD("render surface create ... ");
mWindowSurface->makeCurrent();
if( mFilter==NULL) {
mFilter = new GpuBaseFilter();
} else {
mFilter->destroy();
}
mFilter->init();
mWindowSurface->swapBuffers();
// ...
}
void GpuFilterRender::surfaceChanged(int width, int height) {
this->mViewWidth = width;
this->mViewHeight = height;
mWindowSurface->makeCurrent();
mFilter->onOutputSizeChanged(width, height);
mWindowSurface->swapBuffers();
}
void GpuFilterRender::surfaceDestroyed() {
if (mWindowSurface) {
mWindowSurface->release();
delete mWindowSurface;
mWindowSurface = NULL;
}
if (mEglCore) {
mEglCore->release();
delete mEglCore;
mEglCore = NULL;
}
}
明らかに、このGpuBaseFilterは重要なクラスであり、フィルターレンダリングプロセス全体で重要な役割を果たします。
したがって、このGpuBaseFilterの機能をすぐに確認してください。
#ifndef GPU_NORMAL_FILTER_HPP
#define GPU_NORMAL_FILTER_HPP
#define FILTER_TYPE_NORMAL 0x1010
/**
* Filter基础类,支持YUV / RGB渲染模式。
*/
class GpuBaseFilter {
public:
// 用于上层获取滤镜列表对应的Filter类型
virtual int getTypeId() { return FILTER_TYPE_NORMAL; }
GpuBaseFilter()
{
NO_FILTER_VERTEX_SHADER = "attribute vec4 position;\n\
attribute vec4 inputTextureCoordinate;\n\
varying vec2 textureCoordinate;\n\
void main()\n\
{\n\
gl_Position = position;\n\
textureCoordinate = inputTextureCoordinate.xy;\n\
}";
NO_FILTER_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\
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\
gl_FragColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
//gl_FragColor = texture2D(SamplerY/U/V, textureCoordinate);\n\
}";
}
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);
mGLAttribPosition = static_cast<GLuint>(glGetAttribLocation(mGLProgId, "position"));
mGLUniformSampleRGB = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerRGB"));
mGLUniformSampleY = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerY"));
mGLUniformSampleU = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerU"));
mGLUniformSampleV = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "SamplerV"));
mGLAttribTextureCoordinate = static_cast<GLuint>(glGetAttribLocation(mGLProgId, "inputTextureCoordinate"));
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;
glUseProgram(mGLProgId);
// runPendingOnDrawTasks();
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);
// onDrawArraysPre();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(mGLAttribPosition);
glDisableVertexAttribArray(mGLAttribTextureCoordinate);
glBindTexture(GL_TEXTURE_2D, 0);
}
// GLUniformSampleRGB对应的draw,我这里没用rgb模式.
//virtual void onDraw2(GLuint textureId, void* positionCords, void* textureCords)
//{
// if (!mIsInitialized)
// return;
// glUseProgram(mGLProgId);
// // runPendingOnDrawTasks();
// glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
// glEnableVertexAttribArray(mGLAttribPosition);
// glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
// glEnableVertexAttribArray(mGLAttribTextureCoordinate);
// if (textureId != -1) {
// glActiveTexture(GL_TEXTURE0);
// glBindTexture(GL_TEXTURE_2D, textureId);
// glUniform1i(mGLUniformSampleRGB, 0);
// }
// // onDrawArraysPre();
// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// glDisableVertexAttribArray(mGLAttribPosition);
// glDisableVertexAttribArray(mGLAttribTextureCoordinate);
// glBindTexture(GL_TEXTURE_2D, 0);
//}
// 相关滤镜对应的可调整参数,通过此借口进行操作
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
長さは少し長いです。これは標準のC ++によって作成された基本クラスです。virtualキーワードで始まる関数は継承されたクラスによって書き直されます(関連する知識ポイントについてはBaiduを参照してください)。レンダリング用のShaderコンストラクターとonDrawメソッドに注目してください。
最初に頂点シェーダーNO_FILTER_VERTEX_SHADERを見てください。
属性vec4位置;
属性vec4inputTextureCoordinate;
さまざまなvec2textureCoordinate;
void main()
{ gl_Position = position; textureCoordinate = inputTextureCoordinate.xy; }
内容は複雑ではなく、ルールに従って頂点座標が出力され、テクスチャ座標がフラグメントシェーダーに渡されます。
次に、この記事の焦点の1つであるNO_FILTER_FRAGMENT_SHADERを見てください。
精密ミディアムフロート;
変化するhighpvec2 textureCoordinate;
均一なsampler2DSamplerRGB;
均一なsampler2DSamplerY;
均一なsampler2DSamplerU;
均一なsampler2DSamplerV;
MAT3 colorConversionMatrix = MAT3(
1.0、1.0、1.0、
0.0、-0.39465、2.03211、
1.13983、-0.58060、0.0)。
vec3 yuv2rgb(vec2 pos)
{ vec3 yuv; yuv.x = texture2D(SamplerY、pos).r; yuv.y = texture2D(SamplerU、pos).r-0.5; yuv.z = texture2D(SamplerV、pos).r-0.5; colorConversionMatrix * yuvを返します。} void main(){
gl_FragColor = vec4(yuv2rgb(textureCoordinate)、1.0);
// gl_FragColor = texture2D(SamplerRGB、textureCoordinate);
}
ここでは明らかに両手で準備します。SamplerRGBはカラーテクスチャです。gl_FragColorをコメントアウトするために使用するコードは、テクスチャ座標から直接派生しています。SamplerY/ U / Vは、3つのコンポーネントがyuv形式の3つのコンポーネントに別々に保存されていることを示す名前でわかります。 、次に、yuvからrgbの一般式を使用します。
R = Y + 0 * U + 1.140 * V
G = Y-0.395 * U-0.581 * V
B = Y + 2.032 * U + 0 * V
上記の式は、定量化されていない10進形式です。定量化とは何ですか?
量子化とは、量子化前にY / U / V〜(0-255)、量子化後のパラメータをY〜(16,235)U〜(16-240)Vと仮定して、線形変換によりYまたはUまたはVを特定の範囲内にすることです。 〜(16-240)、Y(0,255)をY '(16,235)に変更し、次のようにします:Y' = Y * [(235-16)/ 255] +16。
もう1つのポイントは、OpenGLマトリックスは列優先であるため、マトリックスをカスタマイズするときは、行と列の交換に注意を払う必要があるということです。
シェーダーの準備ができたら、次のステップはNV21のレンダリング方法です。
void GpuFilterRender::renderOnDraw(double elpasedInMilliSec)
{
if (mEglCore == NULL || mWindowSurface == NULL) {
LOGW("Skipping drawFrame after shutdown");
return;
}
pthread_mutex_lock(&mutex);
ByteBuffer* item = mNV21Pool.get();
if(item == NULL) {
pthread_mutex_unlock(&mutex);
return;
} else { // item!=NULL,i420BufferY/U/V也!=NULL
int8_t * nv21_buffer = item->data();
int y_len = item->param1;
int u_len = item->param2;
int v_len = item->param3;
// 装填y u v数据。
int8_t * dst_y = i420BufferY->data();
int8_t * dst_u = i420BufferU->data();
int8_t * dst_v = i420BufferV->data();
memcpy(dst_y, nv21_buffer, (size_t) y_len);
for (int i = 0; i < u_len; i++) {
//NV21 先v后u
*(dst_v + i) = (uint8_t) *(nv21_buffer + y_len + i * 2);
*(dst_u + i) = (uint8_t) *(nv21_buffer + y_len + i * 2 + 1);
}
// 删除BufferPool当中的引用。
delete item;
pthread_mutex_unlock(&mutex);//#赶紧解锁
// 画面渲染
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);
if( mFilter!=NULL) {
mFilter->onDraw(yTextureId, uTextureId, vTextureId, positionCords, textureCords);
}
mWindowSurface->swapBuffers();
}
}
GLuint GpuFilterRender::updateTexture(int8_t *src, int texId, int width, int height)
{
GLuint mTextureID;
if( texId == -1) {
glGenTextures(1, &mTextureID);
glBindTexture(GL_TEXTURE_2D, mTextureID);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, src);
} else {
glBindTexture(GL_TEXTURE_2D, texId);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
GL_LUMINANCE, GL_UNSIGNED_BYTE, src);
mTextureID = texId;
}
glBindTexture(GL_TEXTURE_2D, 0);
return mTextureID;
}
feedVideoDataのときにnv21データをバッファプールmNV21Poolに入力することを思い出してください。GLThread-> renderOnDrawのコールバックでは、nv21を取得し続け、NV21形式(YYYYVU ...)に従ってY / U / Vに分解します。 3つのバッファを作成し、updateTextureメソッドを使用してテクスチャオブジェクトを時間内に更新します。エラーが発生しやすい場所があります。Ybufferの長さと幅は通常のFrameSizeですが、UVの長さと幅はFrameSize / 2です。対応する正しい長さと幅がない場合、glTexImage2D / glTexSubImage2DはSIGSEGVエラーとクラッシュを報告します。後で、GpuBaseFilter.onDrawを介してレンダリングできます。
virtual void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
void* positionCords, void* textureCords)
{
if (!mIsInitialized)
return;
glUseProgram(mGLProgId);
//绑定顶点坐标 和 纹理坐标
glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
glEnableVertexAttribArray(mGLAttribPosition);
glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
glEnableVertexAttribArray(mGLAttribTextureCoordinate);
// 把Y/U/V分别绑定三个纹理对象
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);
// 经过shader的渲染
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(mGLAttribPosition);
glDisableVertexAttribArray(mGLAttribTextureCoordinate);
glBindTexture(GL_TEXTURE_2D, 0);
}
プロセス全体がここで行われ、画面の比率に適応するカメラのビデオソースがすでに表示されています。次の章の主な内容は、3つの単純なフィルター効果を設定することにより、フィルターをシームレスに切り替えるためのユニバーサルな方法を提供することです。
プロジェクトアドレス:https://github.com/MrZhaozhirong/NativeCppApp