国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

詳解有關Android截圖與錄屏功能的學習

2019-10-23 18:32:06
字體:
來源:轉載
供稿:網友

簡單的截屏和錄屏功能。

因為MediaProjection是5.0以上才出現的,所以今天所講述功能實現,只在5.0以上的系統有效。

截屏:

步驟如下:

1:獲取MediaProjectionManager

2:通過MediaProjectionManager.createScreenCaptureIntent()獲取Intent

3:通過startActivityForResult傳入Intent然后在onActivityResult中通過MediaProjectionManager.getMediaProjection(resultCode,data)獲取MediaProjection

4:創建ImageReader,構建VirtualDisplay

5:最后就是通過ImageReader截圖,就可以從ImageReader里獲得Image對象。

6:將Image對象轉換成bitmap

實現:

步驟已經給出了,我們就按照步驟來實現代碼吧。

首先MediaProjectionManager是系統服務,我們通過getSystemService(MEDIA_PROJECTION_SERVICE)獲取它

 

復制代碼 代碼如下:

projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);

 

然后調用startActivityForResult傳入projectionManager.createScreenCaptureIntent()創建的Intent

 

復制代碼 代碼如下:

startActivityForResult(projectionManager.createScreenCaptureIntent(),SCREEN_SHOT);

 

緊接著我們就可以在onActivityResult(int requestCode, int resultCode, Intent data)中通過resultCode和data來獲取MediaProjection

  @Override  protected void onActivityResult(int requestCode, int resultCode, Intent data) {    if(requestCode == SCREEN_SHOT){      if(resultCode == RESULT_OK){        //獲取MediaProjection        mediaProjection = projectionManager.getMediaProjection(requestCode,data);      }    }  }

然后就是創建ImageReader和VirtualDisplay

    imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);    if(imageReader!=null){      Log.d(TAG, "imageReader Successful");    }    mediaProjection.createVirtualDisplay("ScreenShout",        width,height,dpi,        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,        imageReader.getSurface(),null,null);

這里我們依次講解一下。

首先是ImageReader.newInstance方法:

 

復制代碼 代碼如下:

public static ImageReader newInstance(int width, int height, int format, int maxImages)

 

方法里接收四個參數。
前兩個width,height是用來指定生成圖像的寬和高。

第三個參數format是圖像的格式,這個格式必須是ImageFormatPixelFormat中的一個,這兩個Format里有很多格式,大家可以點進去看看,我們例子中使用的是PixelFormat.RGBA_8888格式(需要注意的是并不是所有的格式都被ImageReader支持,比如說ImageFormat.NV21)。

第四個參數是maxImages,這個參數指的是你想同時在ImageReader里獲取到的Image對象的個數,這個參數我不是很懂,我不理解同時的意思。我的理解是ImageReader是一個類似數組的東西,然后我們可以通過acquireLatestImage()或acquireNextImage()方法來得到里面的Image對象(可能有誤,僅供參考)。這個值應該設置的越小越好,但是得大于0,所以我們上面設置的是1。

然后我們看看mediaProjection.createVirtualDisplay方法:

createVirtualDisplay(@NonNull String name,      int width, int height, int dpi, int flags, @Nullable Surface surface,      @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler)

首先這個方法返回的是VirtualDisplay。

前四個不用說了,分別是VirtualDisplay的名字,寬,高和dpi。

第五個參數,大家可以點 DisplayManager查看所有的flags,我沒有具體的研究過,在本次要實現的例子里,除了VIRTUAL_DISPLAY_FLAG_SECURE這個會報錯,其他的flags效果都一樣。

第六個參數,是一個Surface。我這里表達一下我的理解,當VirtualDisplay被創建出來時,也就是createVirtualDisplay調用后,你在真實屏幕上的每一幀都會輸入到Surface參數里。也就是說,如果你放個SurfaceView,然后傳入SurfaceView的Surface那么你在屏幕上的操作都會顯示在SurfaceView里(這里我們后面錄屏會講)。我們這里傳入的是ImageReader的Surface。這其中的邏輯我的理解是這樣的,真實屏幕的每一幀都都會傳給ImageReader,根據ImageReader的maxImages參數,比如說maxImages是2,那么ImageReader始終保持兩幀圖片,但這兩幀圖片是一直隨著真實屏幕的操作而更新的(不知道大家有沒有聽懂)。

第七個參數,是一個回調函數,在VirtualDisplay狀態改變時調用。因為我們這里沒有,所以傳null。

第八個參數,這里我給出原文:“The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread's main Looper.”因為我翻譯不好。不過和普通的Handler使用場景類似。

現在我們ImageReader和VirtualDisplay,接下來我們就可以通過ImageReader的acquireLatestImage()或acquireNextImage()來得到Image對象了。

SystemClock.sleep(1000);Image image = imageReader.acquireNextImage();

這里有個坑,就是你在獲取Image的時候,得先暫停1秒左右,不然就會獲取失敗(原因未知)。

現在我們有了Image對象,但是Image對象并不能直接作為UI資源被使用,我們可以將它轉換成Bitmap對象。

    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);    image.close();

這里最主要的邏輯就是像素與字節的轉換,我們需要將Image對象的字節流寫進Bitmap里,但是Bitmap接收的是像素格式的。
我們一行一行來看:

首先獲取image對象的寬和高,注意width和height是像素格式的。

然后獲取ByteBuffer,里面存放的就是圖片的字節流,是字節格式的。我是這么理解的,ByteBuffer里面是一長串的字節序列,按照某種格式分成行列就變成了圖片。

然后獲取PixelStride,這指的是兩個像素的距離(就是一個像素頭部到相鄰像素的頭部),這是字節格式的。

RowStride是一行占用的距離(就是一行像素頭部到相鄰行像素的頭部),這個大小和width有關,這里需要注意,因為內存對齊的原因,所以每行會有一些空余。這個值也是字節格式的。

緊接著我們需要創建一個Bitmap用來接受Image的buffer的輸入,buffer是字節流,它會按照我們設置的format轉換成像素,所以這里最重要的一個地方就是Bitmap創建的大小,因為高度就是行數所以就是height,但是寬度因為上面說的內存對齊問題會有些空余,所以我們要先求出空余部分,然后加上width。

int rowPadding = rowStride - pixelStride * width;

這句話用整行的距離減去了一行里像素及空隙占用的距離,剩下的就是空余部分。但是這個是字節格式的。我們將它除以pixelStride,也就是一個像素及空隙占用的字節大小,就轉換成了像素格式。
然后:

width + rowPadding / pixelStride

這個就是一行里像素的占用了,我們將它傳給Bitmap:

 

復制代碼 代碼如下:

bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);

 

創建出合適大小的Bitmap,然后把Image的buffer傳給它,就成功的將Image對象轉換成了Bitmap。

這里我可能講的不清楚,我給大家畫了張圖:

android,錄屏實現,錄屏,android截圖功能

上面的一小格一小格是一塊塊像素。

好了,現在我們已經獲取到了bitmap了,我們可以把它放到ImageView里顯示一下,我寫了一個例子,效果如下:

android,錄屏實現,錄屏,android截圖功能

點擊按鈕,彈出一個對話框請求截屏,點擊立即開始的話,截屏就會顯示在下面的ImageView里。

截屏就這樣,我已經盡力了,╮(╯▽╰)╭

錄屏:

步驟:

錄屏的前三步和截屏是一樣的,出現分歧點的地方在于VirtualDisplay創建時傳入的Surface,還記得我們上面說的嗎,說在創建VirtualDisplay的時候,傳入一個SurfaceView的Surface的話,那么你在真實屏幕上的操作,都會重現在SurfaceView上。我們來試一下:

mediaProjection.createVirtualDisplay("ScreenShout",        width,height,dpi,        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,        surfaceView.getHolder().getSurface(),null,null);

我們在Surface參數中傳入一個SurfaceView的Surface

效果如下:

android,錄屏實現,錄屏,android截圖功能

可以看到我們放了一個Button,放了一個ImageView,放了一個SurfaceView。

點擊Button,然后點立即開始之后,真實屏幕就映射到了SurfaceView里。

所以當創建VirtualDisplay時,真實屏幕就映射到了Surface,也就是我們可以再Surface里拿到屏幕的一個輸入。那我們要錄屏的話,就只要把Surface轉換成我們需要的格式就行了,在本篇文章的例子中,我們會將Surface對象轉換成mp4格式。這就需要用到MediaCodec類和MediaMuxer類。MediaCodec生成一個Surface用來接收屏幕的輸出并按照格式編碼,然后傳給MediaMuxer用來封裝成mp4格式的視頻。

    //第一個參數是mime類型,我們傳入video/avc    //第二第三個參數是寬和高    MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);    //COLOR_FormatSurface這里表明數據將是一個graphicbuffer元數據    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);    //設置碼率,碼率越大視頻越清晰,相對的占用內存也要更大    format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);    //設置幀數    format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);    //設置兩個關鍵幀的間隔,這個值你設置成多少對我們這個例子都沒啥影響    //這個值做視頻的朋友可能會懂,反正我不是很懂,大概就是你預覽的時候,比如你設置為10,那么你10秒內的預覽圖都是同一張    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);    //創建一個MediaCodec實例    mediaCodec = MediaCodec.createEncoderByType("video/avc");    //第一個參數將我們上面設置的format傳進去    //第二個參數是Surface,如果我們需要讀取MediaCodec編碼后的數據就要傳,但我們這里不需要所以傳null    //第三個參數關于加解密的,我們不需要,傳null    //第四個參數是一個確定的標志位,也就是我們現在傳的這個    mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);    //獲取MediaCodec的surface,這個surface其實就是一個入口,屏幕作為輸入源就會進入這個入口,然后交給MediaCodec編碼    surface = mediaCodec.createInputSurface();    mediaCodec.start();

上面講了MediaCodec的創建,我們也可以從中看到屏幕數據是怎么進入MediaCodec的。具體的我已經注釋了。

接下來我們創建一個MediaMuxer對象:

//第一個參數是輸出的地址//第二個參數是輸出的格式,我們設置的是mp4格式mediaMuxer = new MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

然后創建VirtualDisplay,把MediaCodec的surface傳進去:

virtualDisplay = mediaProjection.createVirtualDisplay(TAG + "-display",              width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,              surface, null, null);

最后就是視頻的編碼與轉換MP4還有保存了:

  private void recordVirtualDisplay() {    while (!mQuit.get()) {      //dequeueOutputBuffer方法你可以這么理解,它會出列一個輸出buffer(你可以理解為一幀畫面),返回值是這一幀畫面的順序位置(類似于數組的下標)      //第二個參數是超時時間,如果超過這個時間了還沒成功出列,那么就會跳過這一幀,去出列下一幀,并返回INFO_TRY_AGAIN_LATER標志位      int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);      //當格式改變的時候嗎,我們需要重新設置格式      //在本例中,只第一次開始的時候會返回這個值      if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {        resetOutputFormat();      } else if (index >= 0) {//這里說明dequeueOutputBuffer執行正常        //這里執行我們轉換成mp4的邏輯        encodeToVideoTrack(index);        mediaCodec.releaseOutputBuffer(index, false);      }    }  }  //這里是將數據傳給MediaMuxer,將其轉換成mp4  private void encodeToVideoTrack(int index) {    //通過index獲取到ByteBuffer(可以理解為一幀)    ByteBuffer encodedData = mediaCodec.getOutputBuffer(index);    //當bufferInfo返回這個標志位時,就說明已經傳完數據了,我們將bufferInfo.size設為0,準備將其回收    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {      bufferInfo.size = 0;    }    if (bufferInfo.size == 0) {      encodedData = null;    }     if (encodedData != null) {      encodedData.position(bufferInfo.offset);//設置我們該從哪個位置讀取數據      encodedData.limit(bufferInfo.offset + bufferInfo.size);//設置我們該讀多少數據      //這里將數據寫入      //第一個參數是每一幀畫面要放置的順序      //第二個是要寫入的數據      //第三個參數是bufferInfo,這個數據包含的是encodedData的offset和size      mediaMuxer.writeSampleData(videoTrackIndex, encodedData, bufferInfo);    }  }  //這個方法其實就是設置MediaMuxer的Format  private void resetOutputFormat() {    //將MediaCodec的Format設置給MediaMuxer    MediaFormat newFormat = mediaCodec.getOutputFormat();    //獲取videoTrackIndex,這個值是每一幀畫面要放置的順序    videoTrackIndex = mediaMuxer.addTrack(newFormat);    mediaMuxer.start();    muxerStarted = true;  }

好了,錄屏到此結束了。

我們來看下實例演示:

android,錄屏實現,錄屏,android截圖功能

總結:

這篇博客寫的真是費時費力,果然水平未到就不該強行寫文。

我不知道我是不是寫清楚了,但還是希望大家看了之后能有一絲絲的收獲,這就是對我最大的鼓勵。

本篇博客的錄屏代碼參考自:ScreenRecorder

本篇博客的實例代碼:

github項目地址:https://github.com/ChenTianSaber/ScreenRecorderShoter

源碼下載地址:ScreenRecorderShoter.rar

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 广昌县| 罗城| 阿图什市| 房产| 陈巴尔虎旗| 醴陵市| 安溪县| 元朗区| 双辽市| 年辖:市辖区| 通化县| 永定县| 万宁市| 天长市| 凤冈县| 庆城县| 财经| 吴旗县| 广宁县| 普兰县| 罗山县| 乌苏市| 汨罗市| 拉孜县| 峨山| 彰武县| 肇东市| 安徽省| 宁都县| 青阳县| 潢川县| 阳高县| 永清县| 石棉县| 西宁市| 台湾省| 克拉玛依市| 若尔盖县| 宜兰市| 瓮安县| 宜兰市|