MediaProjection
android在5.0系统之前,是没有开放视频录制的接口的,如果要录制视频,必须要先root。
在5.0,Google终于开放了视频录制的接口(其实严格来说,是屏幕采集的接口),也就是MediaProjection
和MediaProjectionManager
。
首先来说MediaProjectionManager,它是一个系统级的服务,类似WindowManager,AlarmManager等,你可以通过getSystemService方法来获取它的实例:
MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
获取到实例后,录像的过程如下
首先:
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);
createScreenCaptureIntent方法的注释如下:
/**
* Returns an Intent that <b>must</b> passed to startActivityForResult()
* in order to start screen capture. The activity will prompt
* the user whether to allow screen capture. The result of this
* activity should be passed to getMediaProjection.
*/
大致意思是,这个方法会返回一个intent,你可以通过startActivityForResult方法来传递这个intent,为了能开始屏幕捕捉,activity会提示用户是否允许屏幕捕捉(为了防止开发者做一个木马,来捕获用户私人信息),你可以通过getMediaProjection来获取屏幕捕捉的结果。
createScreenCaptureIntent的代码我们可以看一下:
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
i.setClassName("com.android.systemui",
"com.android.systemui.media.MediaProjectionPermissionActivity");
return i;
}
所以这里是创建了一个隐式的intent,用来调用系统的录屏程序。
然后正如上面的注释所说,我们通过startActivityForResult来传递这个intent,所以我们可以通过onActivityResult来获取结果,通过getMediaProjection来取出intent中的数据:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != PERMISSION_CODE) {
Log.e(TAG, "Unknown request code: " + requestCode);
return;
}
if (resultCode != RESULT_OK) {
Toast.makeText(this,
"User denied screen sharing permission", Toast.LENGTH_SHORT).show();
return;
}
mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
mVirtualDisplay = createVirtualDisplay();
}
我们通过getMediaProjection获取到mediaProjection,并注册了一个callback回调。
看看createVirtualDisplay做了什么:
private VirtualDisplay createVirtualDisplay() {
return mMediaProjection.createVirtualDisplay("ScreenSharingDemo",
mDisplayWidth, mDisplayHeight, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mSurface, null /*Callbacks*/, null /*Handler*/);
}
可以看到,我们调用了MediaProjection的createVirtualDisplay方法,来创建了一个VirtualDisplay的实例,说几个createVirtualDisplay的参数含义:
name: 是生成的VirtualDisplay实例的名称;
width, height: 分别是生成实例的宽高,必须大于0;
dpi: 生成实例的像素密度,必须大于0,一般都取1;
surface: 这个比较重要,是你生成的VirtualDisplay的载体,
我的理解是,VirtualDisplay的内容是一帧帧的屏幕截图(所以你看到是有宽高,像素密度等设置),
所以MediaProjection获取到的其实是一帧帧的图,然后通过 surface(surface你可以理解成是android的一个画布,
默认它会以每秒60帧来刷新,这里我们不再展开细说),来顺序播放这些图片,形成视频。
这样,屏幕所捕获的内容,就显示在这个SurfaceView上面了
Android 实现录屏推流的方案整理
一、录屏推流实现的步骤
采集数据
主要是采集屏幕获得视频数据,采集麦克风获得音频数据
,如果可以实现的话,我们还可以采集一些应用内置的音频数据。数据格式转换
主要是将获取到的视频和音频转换成常见的推流的标准格式
,这样能保证让观看终端正常观看。编码处理
如果不进行编码的话,数据量会非常大,这样不仅浪费带宽,而且会浪费观看终端的性能,所以需要对音视频数据进行编码处理
。封包&推流
这块的逻辑可以采用和普通的直播方式进行封装和推流
。
总结:其实录屏推流直播和普通的直播的区别就是采集源发生了变化,而在技术层面来将真正需要我们做的事情就是将录屏获取到的数据处理成稳定的编码格式。
二、Android 实现录屏的思路
主流实现思路:
MediaProjection + VirtualDisplay
方法
在Android 5.0 发布后,谷歌开放了截屏的接口,我们可以通过VirtualDisplay来实现录屏的视频源数据的获取。需ROOT思路:读取 /dev/graphics/fb0 方法 or screencap -p xxx.png/screenshot xxx.png 方法
Android 基于 Linux,所以可以通过读取 fb0 设备节点,即 framebuffer 中的帧数据来实现屏幕的录制。但是读取这个设备节点需要 root 权限。
screencap 是 Android shell 的命令,可以通过该 shell 命令读取到屏幕的帧数据来达到录屏的功能。但该命令仍然需要 root 权限。