標籤:position track pre 編解碼 解碼器 file callback and
http://blog.csdn.net/wds1181977/article/details/52174840
MediaProjection介紹
MediaProjection可以用來捕捉螢幕,具體來說可以截取當前螢幕和錄製螢幕視頻。MediaProjection由MediaProjectionManager來管理和擷取。
使用步驟
首先擷取MediaProjectionManager,和其他的Manager一樣通過 Context.getSystemService() 傳入參數MEDIA_PROJECTION_SERVICE獲得執行個體。
接著調用MediaProjectionManager.createScreenCaptureIntent()彈出dialog詢問使用者是否授權應用捕捉螢幕,同時覆寫onActivityResult()擷取授權結果。
如果授權成功,通過MediaProjectionManager.getMediaProjection(int resultCode, Intent resultData)擷取MediaProjection執行個體,通過MediaProjection.createVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, VirtualDisplay.Callback callback, Handler handler)建立VirtualDisplay執行個體。實際上在上述方法中傳入的surface參數,是真正用來截屏或者錄屏的。
截屏
截屏這裡用到ImageReader類,這個類的getSurface()方法擷取到surface直接傳入MediaProjection.createVirtualDisplay()方法中,此時就可以執行截取。通過ImageReader.acquireLatestImage()方法即可擷取當前螢幕的Image,經過簡單處理之後即可儲存為Bitmap。
private void startCapture() { mImageName = System.currentTimeMillis() + ".png"; Log.e(TAG, "image name is : " + mImageName); Image image = mImageReader.acquireLatestImage(); 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 = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); image.close(); if (bitmap != null) { Log.e(TAG, "bitmap create success "); try { File fileFolder = new File(mImagePath); if (!fileFolder.exists()) fileFolder.mkdirs(); File file = new File(mImagePath, mImageName); if (!file.exists()) { Log.e(TAG, "file create success "); file.createNewFile(); } FileOutputStream out = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); Log.e(TAG, "file save success "); Toast.makeText(this.getApplicationContext(), "成功", Toast.LENGTH_SHORT).show(); } catch (IOException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } }
錄屏
錄屏需要用到上篇博文中的MediaCadec,這個類將原始的螢幕資料編碼,在通過MediaMuxer分裝為mp4格式儲存。MediaCodec.createInputSurface()擷取一個surface對象講起傳入MediaProjection.createVirtualDisplay()即可擷取螢幕原始多媒體資料,之後讀取MediaCodec編碼輸出資料經過MediaMuxer封裝處理為mp4即可播放,實現錄屏。
private void recordVirtualDisplay() {//迴圈多去轉碼器輸出資料經過處理儲存為mp4 while (!mIsQuit.get()) { int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000); Log.i(TAG, "dequeue output buffer index=" + index); if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {//後續輸出格式變化 resetOutputFormat(); } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {//請求逾時 Log.d(TAG, "retrieving buffers time out!"); try { // wait 10ms Thread.sleep(10); } catch (InterruptedException e) { } } else if (index >= 0) {//有效輸出 if (!mMuxerStarted) { throw new IllegalStateException("MediaMuxer dose not call addTrack(format) "); } encodeToVideoTrack(index); mMediaCodec.releaseOutputBuffer(index, false); } } } private void encodeToVideoTrack(int index) {//輸出資料為mp4檔案 ByteBuffer encodedData = mMediaCodec.getOutputBuffer(index); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {//是特定格式資訊等配置資料,不是媒體資料 // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. // Ignore it. Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); mBufferInfo.size = 0; } if (mBufferInfo.size == 0) { Log.d(TAG, "info.size == 0, drop it."); encodedData = null; } else { Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size + ", presentationTimeUs=" + mBufferInfo.presentationTimeUs + ", offset=" + mBufferInfo.offset); } if (encodedData != null) {//存在編碼資料 encodedData.position(mBufferInfo.offset); encodedData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);//寫入 Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer..."); } } private void resetOutputFormat() { // should happen before receiving buffers, and should only happen once if (mMuxerStarted) { throw new IllegalStateException("output format already changed!"); } MediaFormat newFormat = mMediaCodec.getOutputFormat(); Log.i(TAG, "output format changed.\n new format: " + newFormat.toString()); mVideoTrackIndex = mMuxer.addTrack(newFormat); mMuxer.start(); mMuxerStarted = true; Log.i(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex); }
附錄參考
官方文檔
Android視頻錄製
Android 5.0截屏
Android錄屏代碼
本文Demo
Android5.0免Root截屏,錄屏