其他
Android Surface截图方法总结
本文作者
作者:时光少年
链接:
https://juejin.cn/post/7398748051878084648
本文由作者授权发布。
前言
实际上,Android Surface截图实际上不算什么难事,在Android N版本开始,系统就已经提供了PixelCopy类来截取Surface,同时还支持缩放,也就是会根据传入的Bitmap大小进行缩放,这种方式可以辅助我们实现画面调整。
SurfaceView截图问题
TextureView截图问题
public static void request(@NonNull Window source, @NonNull Bitmap dest,
@NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
request(source, null, dest, listener, listenerThread);
}
PixelCopy的缺陷
PixelCopy性能如何?
主线程截取图片,在子线程中发送图片结果
public static void request(@NonNull Surface source, @Nullable Rect srcRect,
@NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
@NonNull Handler listenerThread) {
validateBitmapDest(dest);
if (!source.isValid()) {
throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
}
if (srcRect != null && srcRect.isEmpty()) {
throw new IllegalArgumentException("sourceRect is empty");
}
// TODO: Make this actually async and fast and cool and stuff
int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest); //在主线程截图
listenerThread.post(new Runnable() {
@Override
public void run() {
listener.onPixelCopyFinished(result); //发送到其他线程
}
});
}
总之,这个方法性能还是可以的,然而,如果我们要实现定时截图,那么需要对Bitmap进行池化,使得Bitmap能够被复用。
private Bitmap captureBitmap() {
int width = getWidth();
int height = getHeight();
int size = width * height * 4;
if(size <= 0) return null;
try {
if(mPBufferPixels == null || mPBufferPixels.capacity() != size) {
mPBufferPixels = ByteBuffer.allocateDirect(size)
.order(ByteOrder.nativeOrder());
}else{
mPBufferPixels.reset(); //重置position
}
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, width, height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
checkGlError("glReadPixels");
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(mPBufferPixels);
return bitmap;
}catch (Throwable e){
e.printStackTrace();
}
return null;
}
前面说过,Android 7之后可以使用PixelCopy工具,但是Android 7之前的版本呢?
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, densityDpi);
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
return createVirtualDisplay(null /* projection */, builder.build(), callback, handler);
}
imageReader = ImageReader.newInstance(1280, 720, ImageFormat.YUV_420_888, 3);
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if(image == null) return;
Surface surface = videoSurface;
if(surface == null) {
image.close();
return;
}
synchronized (surface){
if(surface.isValid()){
byte[] yuv420SP = ImageBitmapUtil.getBytesFromImageAsType(image, ImageBitmapUtil.NV21);
int[] rgb = ImageBitmapUtil.decodeYUV420SP(yuv420SP, image.getWidth(), image.getHeight());
Bitmap bitmap = Bitmap.createBitmap(rgb, 0, image.getWidth(), image.getWidth(),
image.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = null;
try{
canvas = surface.lockCanvas(null);
canvas.drawBitmap(bitmap,0,0,null);
bitmap.recycle();
}catch(Throwable ignore){
ignore.printStackTrace();
}finally{
if(canvas != null){ //防止绘制时异常,导致无法释放的问题
surface.unlockCanvasAndPost(canvas);
}
}
}
image.close();
}
},new Handler(thread.getLooper()));
ImageReader 风险点
对于ImageReader而言,其本身当然也存在一些风险,比如ImageReader创建的Surface“切剧”或者”“后传递给下一个实例后,可能出现无法收到数据的问题,目前还没定位到原因; ImageReader 在Genymotion模拟器上不支持YUV_420_888,在官方模拟器上只支持RGBA_8888,因此避免在模拟器上使用。
virtualDisplay = displayManager.createVirtualDisplay("AutoProjection", displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi,surface,DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
综上,Android 7.0我们使用PixelCopy ,而低版本可以使用虚拟屏方案。
scrcpy方案
虚拟屏中转方案
Android 11 虚拟屏
Android 11 mirrorSurface
private void createMirror() {
mMirrorSurface = WindowManagerWrapper.getInstance().mirrorDisplay(mDisplayId);
if (!mMirrorSurface.isValid()) {
return;
}
mTransaction.show(mMirrorSurface)
.reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
modifyWindowMagnification(mTransaction);
mTransaction.apply();
}
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!