一、概述

上篇文章提到了在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;}//初始化openglinitOpengl(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不能读取原始分辨率位图的问题。

硬解直显模式实现抓图功能(二)相关推荐

  1. UnRaid利用iGVT-g插件实现Nas宿主机、虚拟机同时使用intel核显输出或硬解(硬件加速)功能

    文章目录 0.前言 1.准备工作 1.1.1台用intel处理器的组建的Nas主机(UnRaid系统必须是6.9.2及以上) 1.2.1台或多台显示器 1.3.1个DisplayLink转接器(USB ...

  2. N5105 软路由安装 ESXi 7 直通核显给 Debian / Ubuntu 虚拟机通过 Docker 实现 jellyfin 硬件转码视频文件(硬解/编码)

    摘要 在ESXi 7.0u3e里直通N5105的核显给虚拟机Debian 11/Ubuntu 22.04(更新到5.18内核),再套用Docker镜像nyanmisaka/jellyfin (10.8 ...

  3. 2.1 黑群晖驱动:10代u核显硬解驱动(解决掉IP、重启无法连接问题)

    本文提供了两种10代核显驱动方式: 1)第一种(本文:二.仅修改i915.ko驱动10代u核显方法)为网上流传最多但是对主板兼容性要求很高,网上评论常会出现操作后无法识别IP(掉IP)的问题.因此,采 ...

  4. win10笔记本独显直连模式怎么开启?

    笔记本开启独显直连可以带给玩家更好的游戏体验,大大提升游戏的流畅运行以及运行帧率.那么下面小编就给大家整理了几个热门品牌的独显直连打开方式,感兴趣的用户快来看看. win10笔记本独显直连模式怎么开启 ...

  5. 使用N5105 第四版小主机 esxi下开启核显硬解emby的尝试

    环境:N5105第四版+esxi 7.0u3sf+frdora 5.19+docker emby 问题:使用核显硬解视频 解法:核显直通给frdora来跑docker硬解 背景:一切从chorme不支 ...

  6. PLDroidPlayer 是 Pili 直播 SDK 的安卓播放器。支持所有直播常用的格式,如:RTMP、HLS、FLV。拥有优秀的功能和特性,如:首屏秒开、追帧优化、丰富的数据和状态回调、硬解软解

    PLDroidPlayer 是 Pili 直播 SDK 的安卓播放器.支持所有直播常用的格式,如:RTMP.HLS.FLV.拥有优秀的功能和特性,如:首屏秒开.追帧优化.丰富的数据和状态回调.硬解软解 ...

  7. A6000显卡转换成无显模式,才能使用VGPU功能

    A6000显卡出厂默认是有显模式,若要使用vGPU功能,需要把它转换成无显模式 官方文档说明: 同样的A5000也是, 转换方式: 使用displaymodeselector 工具转换(工具需到NVI ...

  8. Jellyfin开启硬件解码(核显硬解)的方法

    Jellyfin开启硬件解码(核显硬解)的方法

  9. Android 硬编硬解退坑指南

    https://www.jianshu.com/p/7c03ebc0d2a0 Android 硬编硬解退坑指南 _qisen 2018.09.01 17:44 字数 2266 阅读 223评论 2喜欢 ...

最新文章

  1. The Linux Command Line读书笔记(二)
  2. Hadoop源代码eclipse编译指南
  3. 使用Pandas进行变量衍生
  4. python中零碎的一点东西
  5. kinux mysql报错10038_navicat连接linux系统中mysql-错误:10038
  6. jquery可编辑表格(版本二)
  7. Framework Design Studio 发布了
  8. 玩一玩微信公众号开发(一) 接入系统
  9. 【C#】设计模式的学习征途系列文章目录(2019版)
  10. 使用Lucene的新FreeTextSuggester查找长尾建议
  11. FreeBSD 10 将使用 Clang 编译器替换 GCC
  12. oracle升级后报 06502,Oracle数据库升级后报ORA-00704和ORA-39700错误
  13. 《python透明人士,他是凭什么成为主流编程的宠儿?!》python基础语法
  14. 鸿蒙开发版发布会现场直播开奖记录,首发鸿蒙OS 荣耀智慧屏系列发布会直播
  15. git rebase 修改提交信息
  16. 16.Linux 高性能服务器编程 --- 服务器调制,调试和测试
  17. android 100以内的随机数
  18. 计算机网络实验教程金伟祖,基于PDCA循环的计算机网络实验教学改革
  19. numpy矩阵与向量类型的向量乘法
  20. python web py入门(3)-模板

热门文章

  1. VTK图形处理之剪裁
  2. 在SQL Server 2000中设置OPTION (MAXDOP 1) 性能提高问题
  3. python图像的几何运算_《计算机视觉》中的几何变换:Python示例的直观解释
  4. 1077 Kuchiguse (20point(s)) - C语言 PAT 甲级
  5. java list转json报错_一个fastjson转换JSON字符串的报错排查
  6. 纸上得来终觉浅,绝知此事要躬行——Spring boot任务调度
  7. Python爬虫(5)
  8. fzu2198 快来快来数一数
  9. 图解ARP协议(三)ARP防御篇-如何揪出内鬼并优雅的还手
  10. linux ubuntu环境下 android jdk sdk eclipse adt 以及手机连接无法识别的解决方法