在Android13 中录屏有个专门的类 ScreenMediaRecorder.java。我们先看ScreenMediaRecorder#start()方法:
// ScreenMediaRecorder.java void start() throws IOException, RemoteException, RuntimeException { Log.d(TAG, "start recording"); // 准备录屏 prepare(); // 开始录屏 mMediaRecorder.start(); // 开始内部音频录制 recordInternalAudio(); }
这个 ScreenMediaRecorder#start()
在 RecordingService.java
的 onStartCommand() 方法里被调用。
接着看 ScreenMediaRecorder#prepare() 准备录屏:
// ScreenMediaRecorder.java private void prepare() throws IOException, RemoteException, RuntimeException { //Setup media projection // 拉起 MEDIA_PROJECTION_SERVICE IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); IMediaProjectionManager mediaService = IMediaProjectionManager.Stub.asInterface(b); IMediaProjection proj = null; proj = mediaService.createProjection(mUser, mContext.getPackageName(), MediaProjectionManager.TYPE_SCREEN_CAPTURE, false); IBinder projection = proj.asBinder(); mMediaProjection = new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection)); // 创建文件、设置格式 File cacheDir = mContext.getCacheDir(); cacheDir.mkdirs(); mTempVideoFile = File.createTempFile("temp", ".mp4", cacheDir); // 设置媒体记录器 // MediaRecorde 主要提供了一些方法用来支持录屏录音 mMediaRecorder = new MediaRecorder(); // 设置音频源 if (mAudioSource == MIC) { // 设置声音来源,一般传入 MediaRecorder. AudioSource.MIC参数指定录制来自麦克风的声音。(这里如果只录屏可以不设置) mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); } // 设置用于录制的视频来源。如屏幕等 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); // 设置所录制的音视频文件的格式。 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 设置视频 DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getRealMetrics(metrics); int refreshRate = (int) wm.getDefaultDisplay().getRefreshRate(); int[] dimens = getSupportedSize(metrics.widthPixels, metrics.heightPixels, refreshRate); int width = dimens[0]; int height = dimens[1]; refreshRate = dimens[2]; int vidBitRate = width * height * refreshRate / VIDEO_FRAME_RATE * VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO; // 设置视频的编码格式 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setVideoEncodingProfileLevel( MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, MediaCodecInfo.CodecProfileLevel.AVCLevel3); // 设置要拍摄的宽度和视频的高度,最高只能设置640x480 mMediaRecorder.setVideoSize(width, height); // 设置录制视频的捕获帧速率 mMediaRecorder.setVideoFrameRate(refreshRate); // 设置所录制视频的编码位率 mMediaRecorder.setVideoEncodingBitRate(vidBitRate); // 设置录制会话的最长持续时间(以ms为单位) mMediaRecorder.setMaxDuration(MAX_DURATION_MS); // 设置最大文件大小 mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES); // 设置音频 if (mAudioSource == MIC) { // 设置音频编码格式 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS); // 设置所录制视频的编码位率 mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE); } // 设置录制的音频文件的保存位置 mMediaRecorder.setOutputFile(mTempVideoFile); // 准备录制 mMediaRecorder.prepare(); // Create surface mInputSurface = mMediaRecorder.getSurface(); // VirtualDisplay类代表一个虚拟显示器,需要调用DisplayManager 类的 createVirtualDisplay()方法, // 将虚拟显示器的内容渲染在一个Surface控件上,即 捕捉屏幕了。 mVirtualDisplay = mMediaProjection.createVirtualDisplay( "Recording Display", // 实际的流媒体显示实体名字,不能为null; width, // 实际的流媒体显示实体的宽度,单位为像素,必须大于0 height, // 实际的流媒体显示实体的高度,单位为像素,必须大于0; metrics.densityDpi, // 实际的流媒体显示实体的像素密度,单位为dp,必须大于0; DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // 实际的流媒体显示实体标志的结合 mInputSurface, // 播放流媒体的surface实例,可为null, null, // 实际的流媒体显示实体状态改变时的回调方法,可能为null; null); // 调用第 7 个参数回调方法的handler mMediaRecorder.setOnInfoListener(mListener); if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) { mTempAudioFile = File.createTempFile("temp", ".aac", mContext.getCacheDir()); mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(), mMediaProjection, mAudioSource == MIC_AND_INTERNAL); } }
上述 prepare() 方法,主要:进行了拉起后台服务,创建VirtualDisplay用于屏幕参数设定、设置录屏问文件路径等操作;
再接着看ScreenMediaRecorder#end()方法:
// ScreenMediaRecorder.java void end() { //jingtao.guo add try catch for TFBAAA-341 crash try { mMediaRecorder.stop(); mMediaRecorder.release(); mInputSurface.release(); mVirtualDisplay.release(); mMediaProjection.stop(); mMediaRecorder = null; mMediaProjection = null; stopInternalAudioRecording(); Log.d(TAG, "end recording"); } catch (RuntimeException e) { Log.e(TAG, "end recording" + e.getMessage()); } }
end() 方法结束录屏,调用了mMediaRecorder的stop与release方法,stopInternalAudioRecording()方法中通过 mAudio.end()方法来停止录音。
缩略图
在谷歌原生设计中,录屏的缩略图并非是视频的第一帧,而是前20帧中的最大有效帧作为预览图。这种设计的好处是避免了第一帧是黑色或者不清楚,从而使得用户看起来比较清晰,更好辨别出所录的视频内容;