VirtualDisplay
一、介紹
代表一個虛擬顯示器。 虛擬顯示器的內容被渲染到您必須提供給createVirtualDisplay()的Surface 。
二、使用
1、createVirtualDisplay
通常我們使用DisplayManager.createVirtualDisplay()來創建虛擬顯示。
一下是創建虛擬顯示的具體方法:
createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags);
createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler)
參數:
name – 虛擬顯示器的名稱,必須非空。
width – 虛擬顯示的寬度(以像素為單位),必須大於 0。
height – 虛擬顯示器的高度(以像素為單位),必須大於 0。
densityDpi – 以 dpi 為單位的虛擬顯示密度,必須大於 0。
surface – 虛擬顯示器的內容應該被渲染到的表面,如果最初沒有,則為 null。
flags – 虛擬顯示標志的組合: VIRTUAL_DISPLAY_FLAG_PUBLIC 、 VIRTUAL_DISPLAY_FLAG_PRESENTATION 、 VIRTUAL_DISPLAY_FLAG_SECURE 、 VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY或VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR 。
callback - 當VirtualDisplay的狀態改變時調用的回調
handler – 應在其上調用偵聽器的處理程序,如果應在調用線程的 Looper 上調用偵聽器,則為 null。
虛擬顯示的內容被渲染到應用程序提供的Surface 。虛擬顯示的行為取決於提供給此方法的標志。 默認情況下,虛擬顯示被創建為私有的、非展示的和不安全的。 使用某些標志可能需要權限。
2、分析參數flags
1.VIRTUAL_DISPLAY_FLAG_PUBLIC
虛擬顯示標志:創建公共顯示。公共虛擬顯示器
設置此標志時,虛擬顯示是公開的。公共虛擬顯示器的行為與大多數連接到系統的其他顯示器(例如外部或無線顯示器)的行為類似。 應用程序可以在顯示器上打開窗口,系統可以將其他顯示器的內容鏡像到它上面。
2.VIRTUAL_DISPLAY_FLAG_PRESENTATION
虛擬顯示標志:創建演示顯示。演示虛擬顯示
當該標志被設置時,虛擬顯示器被注冊為presentation display category中的presentation display category 。 應用程序可以自動將其內容投影到演示顯示以提供更豐富的第二屏幕體驗。
3.VIRTUAL_DISPLAY_FLAG_SECURE
虛擬顯示標志:創建安全顯示。安全的虛擬顯示器
設置此標志后,虛擬顯示被視為安全。 來電者承諾采取合理措施,例如無線加密,以防止顯示內容被截取或記錄在持久性介質上。
另外:創建安全的虛擬顯示器需要 CAPTURE_SECURE_VIDEO_OUTPUT 權限。 此權限保留供系統組件使用,不適用於第三方應用程序
4.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
虛擬顯示標志:只顯示該顯示器本身的內容; 不要鏡像另一個顯示器的內容。
此標志與VIRTUAL_DISPLAY_FLAG_PUBLIC結合使用。 通常,如果公共虛擬顯示器沒有自己的窗口,它們將自動鏡像默認顯示器的內容。 當這個標志被指定時,虛擬顯示器將只顯示它自己的內容,如果它沒有窗口,它將被消隱。
5.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
虛擬顯示標志:當沒有顯示內容時,允許將內容鏡像到私人顯示器上。此標志僅用於在創建私有顯示時覆蓋默認行為。
另外:創建自動鏡像虛擬顯示器需要 CAPTURE_VIDEO_OUTPUT 或 CAPTURE_SECURE_VIDEO_OUTPUT 權限。 這些權限保留供系統組件使用,第三方應用程序無法使用。 或者,可以使用適當的MediaProjection來創建自動鏡像虛擬顯示。
3、MediaProjection
授予應用程序捕獲屏幕內容和/或錄制系統音頻的能力的令牌。 授予的確切功能取決於 MediaProjection 的類型。
屏幕捕獲會話可以通過MediaProjectionManager.createScreenCaptureIntent啟動。 這允許捕獲屏幕內容,但不能捕獲系統音頻。
示例:
MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (mProjectionManager != null) {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), 1);
}
在onActivityResult回調中處理
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
String path=GetRealPath.getPath(this,uri);
if(requestCode==1){
if(resultCode==RESULT_OK){
MediaProjection mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
VirtualDisplay virtualDisplay =mMediaProjection.createVirtualDisplay("screen_shut", mWidth, mHeight, mDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,mSurface,null,null);
}
}
}
createVirtualDisplay參數:
name: 是生成的VirtualDisplay實例的名稱;
width, height: 分別是生成實例的寬高;
dpi: 生成實例的像素密度,必須大於0,一般都取1;
surface: 這個比較重要,是你生成的VirtualDisplay的載體,我的理解是,VirtualDisplay的內容是一幀幀的屏幕截圖(所以你看到是有寬高,像素密度等設置),所以MediaProjection獲取到的其實是一幀幀的圖,然后通過surface來順序播放這些圖片,形成視頻。
MediaProjection最好在前台服務中創建並在清單文件中設置Service為foregroundServiceType="mediaProjection",以上僅僅為示例
三、使用ImageReader獲取虛擬顯示器中的圖像
1、ImageReader
ImageReader 類允許應用程序直接訪問渲染到Surface圖像數據;圖像數據封裝在Image對象中,可以同時訪問多個這樣的對象,最多可達maxImages構造函數參數指定的數量。 通過其Surface發送到 ImageReader 的新圖像將排隊,直到通過acquireLatestImage或acquireNextImage調用訪問。 由於內存限制,如果 ImageReader 沒有以等於生產速率的速率獲取和釋放圖像,則圖像源最終將在嘗試渲染到 Surface 時停止或丟棄圖像。
2、實例化newInstance
靜態方法直接調用
ImageReader.newInstance(
@IntRange(from = 1) int width,
@IntRange(from = 1) int height,
@Format int format,
@IntRange(from = 1) int maxImages);
width – 此閱讀器將生成的圖像的默認寬度(以像素為單位)。
height – 此閱讀器將生成的圖像的默認高度(以像素為單位)。
格式 - 此閱讀器將生成的圖像格式。 這必須是ImageFormat或android.graphics.PixelFormat常量之一。 請注意,並非所有格式都受支持,例如 ImageFormat.NV21。
maxImages – 用戶希望同時訪問的最大圖像數。 這應該盡可能小以限制內存使用。 一旦用戶獲得了 maxImages 圖像,必須先釋放其中一個圖像,然后才能通過。
3、獲取VirtualDisplay中的圖像
創建virtualdisplay時傳入imageReader.getSurface()
virtualDisplay=mDisplayManager.createVirtualDisplay("getImage", mWidth, mHeight, mDensity, mImageReader.getSurface(),
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
imageReader.getSurface():獲取可用於為此ImageReader生成Images的Surface 。
注冊一個偵聽器,以便在 ImageReader 中有新圖像可用時調用。
注意這里的新圖像,也就是說如果你的虛擬顯示中始終沒有新的操作或者說界面沒有變化,ImageReader是監聽不到新的圖像的,log可能不會打印,但不意味着虛擬顯示中的圖像消失了。
//一般而言,如果你的Handler是要來刷新操作UI的,那么就需要在主線程下跑。
Handler mHandler=new Handler(Looper.getMainLooper());
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(),mHandler);
private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
if (reader != mImageReader) {
return;
}
//SystemClock.sleep(2000);
Image image = reader.acquireNextImage();
if (image != null) {
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
//保存圖片到本地
ImageSaveUtil.saveBitmap2file(bitmap,activity,String.valueOf(System.currentTimeMillis()));
Log.d(TAG, "New image available from virtual display." + i);
image.close();
i++;
}
}
}