如何在GPU端无需拷贝获取Android当前Activity窗口Surface的帧缓冲区?
嘿,我完全懂你的痛点!要30-60FPS的帧率获取Activity画面当OpenGL ES纹理,转Bitmap再上传确实太拖性能了,毕竟多了一次CPU-GPU的数据往返。下面给你俩靠谱的方案,都是GPU内部流转,几乎没拷贝开销:
方案一:用MediaProjection + SurfaceTexture(跨App/通用场景)
这是最通用的方案,Android 5.0(API 21)以上支持,不管是捕获自己App还是其他App的窗口都能用,全程GPU内操作,完全绕开Bitmap:
先搞定屏幕录制权限
得先通过MediaProjectionManager向用户申请屏幕录制权限,代码大概是这样:MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_CAPTURE);在
onActivityResult里拿到用户授权的MediaProjection实例。创建SurfaceTexture并绑定VirtualDisplay
在OpenGL渲染线程里,先创建一个SurfaceTexture(可以传一个你OpenGL上下文里生成的纹理ID,或者让系统自动生成):// 假设你已经有了GL纹理id:textureId SurfaceTexture surfaceTexture = new SurfaceTexture(textureId);然后用MediaProjection创建一个VirtualDisplay,把屏幕输出定向到这个SurfaceTexture:
DisplayMetrics metrics = getResources().getDisplayMetrics(); MediaProjection mediaProjection = ...; // 从onActivityResult拿到的实例 VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenCapture", metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, new Surface(surfaceTexture), null, // 回调可选 null // handler可选 );在OpenGL里直接用这个纹理
每次SurfaceTexture有新帧的时候,在GL线程调用surfaceTexture.updateTexImage(),然后就能用你之前传入的textureId直接渲染了——这一步完全是GPU内部的纹理更新,没有任何CPU参与的拷贝,帧率轻松达标。
方案二:用PixelCopy直接拷贝到OpenGL纹理(仅限自己App窗口)
如果你只需要捕获自己App的Activity窗口,也可以用PixelCopy直接把窗口Surface拷贝到你的OpenGL纹理里,虽然是拷贝,但也是GPU内部的操作,比转Bitmap快得多:
// 假设你有GL纹理id:glTextureId,以及Activity的窗口 Window window = getWindow(); PixelCopy.request(window, null, glTextureId, new PixelCopy.OnPixelCopyFinishedListener() { @Override public void onPixelCopyFinished(int copyResult) { if (copyResult == PixelCopy.SUCCESS) { // 纹理已经更新完成,可以在GL线程里使用了 } } }, new Handler(Looper.getMainLooper()));
不过要注意,PixelCopy是异步的,而且每次拷贝是一次操作,如果你要持续获取帧的话,得在每次拷贝完成后发起下一次请求,帧率可能不如MediaProjection的实时绑定稳定。
一些注意事项
- 线程安全:SurfaceTexture的
updateTexImage()必须在OpenGL渲染线程调用,别在主线程瞎搞。 - 分辨率匹配:创建VirtualDisplay的时候尽量用和原窗口一致的分辨率,避免GPU额外的缩放计算,影响帧率。
- 资源回收:用完MediaProjection记得调用
mediaProjection.stop(),VirtualDisplay也要release(),不然会占系统资源。
备注:内容来源于stack exchange,提问作者dragonfly

