硬解直显模式实现抓图功能
Posted Nipuream
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了硬解直显模式实现抓图功能相关的知识,希望对你有一定的参考价值。
一、概述
上篇文章提到了在android平台中自己创建和构造外部纹理提供给MediaCodec作为输入端接受解码后的数据,并通过onFrameAvailable()方法的回调来更新纹理,并通过opengl的来渲染和屏幕绑定缓存帧来实现。这样做的好处就是有效的减少cpu和gpu的交互,从而提高应用上的体验。但是如果要实现抓图功能,只是使用glReadPixels()方法需要从显存到内存的交换过程时间作为代价的,笔者亲测,抓一张4k高清图像,需要卡顿渲染线程2秒钟左右的时间,这是万万不能接受的;而且在使用glReadPixels方法过程中,发现这个方法只能获取当前窗口显示的区域,并非是原图。接下来,介绍一种利用FBO附着纹理的方式来实现抓图,来减少渲染线程在抓图过程中耗时。
二、实现
首先简单的来介绍FBO,它的全程是 frame buffer object,在它出现之前,opengl 渲染到纹理都使用pbuffer(屏幕外的缓存区域),但是它有一些方面的弊端,所以单独提供一种FBO的方式作为渲染到纹理的方式,它自身并未占用缓存区的内存,而是以附着的方式来绑定纹理。
那么就在聊聊减少渲染线程执行glReadPixels的工作量,这个毋庸置疑肯定是用另外一条线程共享eglcontext,来获取到当前渲染的线程,然后拷贝纹理,拷贝完了之后再通知渲染线程继续执行渲染,然后这条线程在使用FBO绑定这个纹理(确保获取到原图),然后执行glReadPixels方法下载图片。
下面提供一些代码参考:
render->makeCurrent();
render->drawFrame(false);
render->swap();
if(render->capture)
pthread_mutex_lock(&render->getFrameCopier()->preview_lock);
//set frame copier texture.
copier->setTexture(textureId, capture_width, capture_height);
pthread_mutex_lock(&render->getFrameCopier()->mLock);
pthread_cond_signal(&render->getFrameCopier()->mCondition);
pthread_mutex_unlock(&render->getFrameCopier()->mLock);
pthread_cond_wait(&render->getFrameCopier()->preview_condition, &render->getFrameCopier()->preview_lock);
render->capture = false;
pthread_mutex_unlock(&render->getFrameCopier()->preview_lock);
最上面是常规渲染相关的代码封装,如果用户点击了抓图,则先向拷贝线程设置当前的纹理,然后通知拷贝线程去拷贝纹理,然后就在这一直等待拷贝线程拷贝纹理结束,再恢复渲染。(拷贝纹理是在gpu操作的,所以速度很快)。
void frame_copier::setTexture(int texture, int width, int height)
this->inputTexture = texture;
this->width = width;
this->height = height;
//init output txtId.
glGenTextures(1, getOutputTexture());
glBindTexture(GL_TEXTURE_EXTERNAL_OES, *getOutputTexture());
checkGlError("glBindTexture textureId");
glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//线性过滤,使用距离当前渲染像素中心最近的4个纹素加权平均值
glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// S 方向的贴图模式,将纹理坐标限制在 0.0 1.0的范围之内,如果超出了,会边缘拉伸填充处理
glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
LOGI("<----- frame_copier notify copy -----> ");
//拷贝线程启动后所执行的函数地址
void* copyPixels(void *addr)
LOGI("======= frame_copier create copy pixels thread. ==========");
auto copier = reinterpret_cast<frame_copier *>(addr);
if(copier == NULL)
LOGI("frame_copier thread %d exit run.", getpid());
return NULL;
//初始化opengl
initOpengl(copier);
while (!copier->exit)
//这里一直等待渲染线程的拷贝通知
pthread_mutex_lock(&copier->mLock);
LOGI("frame_copier wait render thread set Texture ...");
pthread_cond_wait(&copier->mCondition, &copier->mLock);
LOGI("frame_copier copy textureId %d pixels", copier->getInputTexture());
if(copier->buf != NULL)
delete [] copier->buf;
makeCurrent(copier);
//1. copy preview texture.
copyWithCoords(copier);
pthread_mutex_lock(&copier->preview_lock);
pthread_cond_signal(&copier->preview_condition);
pthread_mutex_unlock(&copier->preview_lock);
//2. bind frame buffer copy pixels.
fboCopy(copier);
//3. copy pixels to Java.
copyToJava(copier);
//unlock.
pthread_mutex_unlock(&copier->mLock);
releaseOpengl(copier);
LOGI("============== frame copier thread exit. ============");
在setTexture()函数里只是获取到渲染线程那边的参数再初始化输出纹理,然后渲染线程知道拷贝线程开始工作,这边首先会拷贝纹理,然后在通过fbo的方式拷贝像素,最后通知Java层,代码中以列举出步骤,接下来逐步贴出参考代码:
void copyWithCoords(frame_copier* copier)
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, *copier->getOutputTexture());
checkGlError("glBindTexture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, *copier->getOutputTexture(), 0);
checkGlError("glFramebufferTexture2D");
glUseProgram(copier->mGLProgId);
glVertexAttribPointer(copier->mGLVertexCoords, 2, GL_FLOAT, GL_FALSE, 0 , mTriangleXYZData);
glEnableVertexAttribArray(copier->mGLVertexCoords);
glVertexAttribPointer(copier->mGLTextureCoords, 2, GL_FLOAT, GL_FALSE, 0, mTriangleUVData);
glEnableVertexAttribArray(copier->mGLTextureCoords);
//binding input texture.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, *copier->getInputTexture());
glUniform1i(copier->mGLUniformTexture, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(copier->mGLVertexCoords);
glDisableVertexAttribArray(copier->mGLTextureCoords);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, 0, 0);
glDeleteFramebuffers(1, &fbo);
拷贝纹理之前通过glCopyTexSubImage2D方法来拷贝,可是最后读取纹理像素的时候,FBO附着失败了。(有知道的同学可以交流下),之后用了着色器来拷贝纹理,这种效率还是非常高的。这步执行完之后,然后立马通知渲染线程继续执行渲染工作,因为我们已经拿到了纹理了,我们拷贝线程可以接下来执行读取像素的工作:
void fboCopy(frame_copier* copier)
if(DEBUG) LOGI("frame_copier capture_width : %d, capture height : %d", copier->width, copier->height);
copier->buf = new unsigned char[copier->width * copier->height * 4];
memset(copier->buf, 0, copier->width * copier->height * 4);
GLuint FBO;
glGenFramebuffers(1, &FBO);
checkGlError("frame_copier glGenFrameBuffers");
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
checkGlError("frame_copier bind framebuffer");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_EXTERNAL_OES, *copier->getInputTexture(), 0);
checkGlError("frame_copier glFramebufferTexture2D");
glActiveTexture(GL_TEXTURE0);
checkGlError("frame_copier glActiveTexture");
glBindTexture(GL_TEXTURE_EXTERNAL_OES, *copier->getInputTexture());
checkGlError("frame_copier bindTexture");
int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(status == GL_FRAMEBUFFER_COMPLETE)
glReadPixels(0,0,(GLsizei)copier->width, (GLsizei)copier->height, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)copier->buf);
checkGlError("frame_copier glReadPixels");
else
if(DEBUG) LOGI("frame_copier framebuffer status : %d", status);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, 0,0);
checkGlError("frame_copier glFrameBufferTexture2D");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
checkGlError("frame_copier glBindFrameBuffer");
glDeleteFramebuffers(1, &FBO);
checkGlError("frame_copier glDeleteFrameBuffers");
最后通知将拷贝出来的buffer,通过jni的方式通知Java层:
void copyToJava(frame_copier* copier)
JNIEnv *env;
if(sVm->AttachCurrentThread(&env, NULL) != JNI_OK)
LOGI("frame_copier attach vm failed.");
return ;
jclass clazz = env->GetObjectClass(copier->obj);
jmethodID copyPixels = env->GetMethodID(clazz, "copyPixelsFromNative", "([B)V");
jbyte *by = (jbyte*)copier->buf;
int size = copier->width * copier->height * 4;
jbyteArray jbyteArray = env->NewByteArray(size);
env->SetByteArrayRegion(jbyteArray, 0, size, by);
env->CallVoidMethod(copier->obj, copyPixels, jbyteArray);
if(DEBUG) LOGI("frame_copier copy pixels to java.");
三、总结
本文主要介绍了,通过另起一条拷贝线程来通过着色器来拷贝纹理,和通过FBO绑定纹理的方式来获取位图的buffer。这种方案有效的解决了获取大图导致渲染卡顿,glReadPixels不能读取原始分辨率位图的问题。
以上是关于硬解直显模式实现抓图功能的主要内容,如果未能解决你的问题,请参考以下文章