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);
}