Android(安卓)MediaProjection截屏与录屏(surfaceview截图)(一)
16lz
2021-01-26
MediaProjection 和 MediaProjectionManager 是 Android 5.0 开放的屏幕截图与录制视频的接口,它可以用来对 surfaceview 进行截图,解决以前 surfaceview 截图出现黑屏的问题(就是问了这个问题来的,5.0以下没找到方法)。
MediaProjectionManager 是一个系统级的服务,可以通过 getSystemService 来获取实例:
MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
然后调用mMediaProjectionManager.createScreenCaptureIntent(),这里会弹出授权的dialog:
startActivityForResult( mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
重写onActivityResult()方法,获取授权结果:
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_MEDIA_PROJECTION) { if (resultCode != Activity.RESULT_OK) { Log.d(TAG, "User cancelled"); Toast.makeText(this, "User cancelled", Toast.LENGTH_SHORT).show(); return; } if (this == null) { return; } Log.d(TAG, "Starting screen capture"); mResultCode = resultCode; mResultData = data; setUpMediaProjection(); setUpVirtualDisplay(); //继续执行截图或者录屏操作 // do somthing... } }
然后创建MediaProjection 的实例:
private void setUpMediaProjection() { mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData); }
截图
接着创建VirtualDisplay实例:
private void setUpVirtualDisplay() { mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture", mWindowWidth, mWindowHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null); }
此处是截图时的代码,所以上面surface参数传入的是: mImageReader.getSurface()
ImageReader 的创建:
ImageReader mImageReader = ImageReader.newInstance(mWindowWidth, mWindowHeight, 0x1, 2);
截图是通过ImageReader.acquireLatestImage() 获取当前屏幕的Image, 然后再获取Bitmap保存:
private void startCapture() { mImageName = System.currentTimeMillis() + ".png"; Log.i(TAG, "image name is : " + mImageName); Image image = mImageReader.acquireLatestImage(); if (image == null) { Log.e(TAG, "image is null."); return; } 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; mBitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); mBitmap.copyPixelsFromBuffer(buffer); mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width, height); image.close(); if (mBitmap != null) { // 保存或者显示... } }
录屏
录屏是通过MediaCadec, 初始化MediaCadec:
private void configureMedia() { MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mWindowWidth, mWindowHeight); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); try { mMediaCodec = MediaCodec.createEncoderByType("video/avc"); } catch (IOException e) { e.printStackTrace(); } mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mSurface = mMediaCodec.createInputSurface(); mMediaCodec.start(); }
创建VirtualDisplay实例:
mVirtualDisplay = mMediaProjection.createVirtualDisplay("record_screen", mWindowWidth, mWindowHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mSurface, null, null);
此处与截图方法出入的surface参数不一样。
开始录屏:
private void startRecord() { try { mMuxer = new MediaMuxer(mVideoPath + System.currentTimeMillis() + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); recordVirtualDisplay(); } catch (IOException e) { e.printStackTrace(); } finally { release(); } } private void recordVirtualDisplay() { while (!mIsQuit.get()) { int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000); Log.d(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 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.d(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); } private void encodeToVideoTrack(int index) { 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 recordStop() { mIsQuit.set(true); } private void release() { mIsQuit.set(false); mMuxerStarted = false; Log.i(TAG, " release() "); if (mMediaCodec != null) { mMediaCodec.stop(); mMediaCodec.release(); mMediaCodec = null; } if (mVirtualDisplay != null) { mVirtualDisplay.release(); mVirtualDisplay = null; } if (mMuxer != null) { mMuxer.stop(); mMuxer.release(); mMuxer = null; } }
这样录屏文件就会保存的指定路径。
部分代码参考他人,不喜可以喷~~
Github:Demo
Android MediaProjection截屏与录屏(ScreenCapture Library 的使用)(二)
更多相关文章
- android 广播详解与实例(Broad Receiver)
- [转]activity的启动方式(launch mode)
- Android(安卓)从源码的角度分析——为什么要用newInstance来实例
- ScrollView嵌套RecyclerView出现item显示不全的问题
- Android(安卓)三种动画详解及简单实例
- android 获取进程、服务、任务列表
- Activity 的四种启动模式(launchMode)
- Android(安卓)UI 之 获取组件或者元素的坐标
- 基于Android中获取资源的id和url方法总结