Flutter Android 实现屏幕采集

上一篇文章我们介绍了 Flutter iOS 实现屏幕采集,今天我们继续介绍 Flutter Android 的屏幕采集的实现。在详细介绍实现流程前,我们先来看看 Android 原生系统提供了哪些能力来进行屏幕录制。

  • Android 5.0 系统提供了 MediaProjection  功能,只需弹窗获取用户的同意即可采集到全局屏幕内容。
  • Android 的 MediaProjection  是直接在 App 主进程内运行的,可以很容易获取到屏幕数据的Surface

虽然无法避免原生代码,但我们可以尽量以最少的原生代码来实现 Flutter 屏幕采集。将两端的屏幕采集能力抽象封装为通用的 Dart 层接口,只需一次部署完成后,就能开心地在 Dart 层启动、停止屏幕采集了。

Android 的实现相对 iOS 比较简单,在启动屏幕采集时,可以直接使用 Flutter 的 MethodChannel 在原生侧通过 MediaProjectionManager 弹出一个向用户请求屏幕采集权限的弹窗,收到确认后即可调用 MediaProjectionManager.getMediaProjection() 函数拿到 MediaProjection 对象。 

需要注意的是,由于 Android 对权限管理日渐收紧,如果你的 App 的目标 API 版本 (Target SDK) 大于等于 29,也就是 Android Q (10.0) 的话,还需要额外启动一个前台服务。根据 Android Q 的迁移文档显示,诸如 MediaProjection 等需要使用前台服务的功能,必须在独立的前台服务中运行。 

首先需要自己实现一个继承 android.app.Service 类,在  onStartCommand 回调中调用上述的 getMediaProjection() 函数获取 MediaProjection 对象。 


@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    int resultCode = intent.getIntExtra("code", -1);
    Intent resultData = intent.getParcelableExtra("data");

    String notificationText = intent.getStringExtra("notificationText");
    int notificationIcon = intent.getIntExtra("notificationIcon", -1);
    createNotificationChannel(notificationText, notificationIcon);

    MediaProjectionManager manager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    MediaProjection mediaProjection = manager.getMediaProjection(resultCode, resultData);
    RequestMediaProjectionPermissionManager.getInstance().onMediaProjectionCreated(mediaProjection, RequestMediaProjectionPermissionManager.ERROR_CODE_SUCCEED);

    return super.onStartCommand(intent, flags, startId);
}

然后还需要在 AndroidManifest.xml 中注册这个类。

<service
    android:name=".internal.MediaProjectionService"
    android:enabled="true"
    android:foregroundServiceType="mediaProjection"
/>

然后在启动屏幕采集时判断系统版本,如果运行在 Android Q 以及更高版本的系统中,则启动前台服务,否则可以直接获取获取 MediaProjection 对象。


@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void createMediaProjection(int resultCode, Intent intent) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        service = new Intent(this.context, MediaProjectionService.class);
        service.putExtra("code", resultCode);
        service.putExtra("data", intent);
        service.putExtra("notificationIcon", this.foregroundNotificationIcon);
        service.putExtra("notificationText", this.foregroundNotificationText);
        this.context.startForegroundService(service);
    } else {
        MediaProjectionManager manager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        MediaProjection mediaProjection = manager.getMediaProjection(resultCode, intent);
        this.onMediaProjectionCreated(mediaProjection, ERROR_CODE_SUCCEED);
    }
}

紧接着,根据业务场景需求从屏幕采集 buffer 的消费者拿到 Surface,例如,要保存屏幕录制的话,从 MediaRecoder 拿到 Surface,要录屏直播的话,可调用音视频直播 SDK 的接口获取 Surface。 有了 MediaProjection  和消费者的 Surface,接下来就是调用 MediaProjection.createVirtualDisplay() 函数传入 Surface 来创建 VirtualDisplay 实例,从而获取到屏幕采集 buffer。

VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture", width, height, 1,
    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, handler);

最后是结束屏幕采集,相比 iOS 复杂的操作,Android 仅需要将  VirtualDisplay  和 MediaProjection 实例对象释放即可。 

实战示例

下面为大家准备了一个实现了 iOS/Android 屏幕采集并使用 ZEGO RTC Flutter SDK 进行推流直播的示例 Demo, 下载链接

ZEGO RTC Flutter SDK 在原生侧提供了视频帧数据的对接入口,可以将上述流程中获取到的屏幕采集 buffer 发送给 RTC SDK 从而快速实现屏幕分享、推流。

Android 端需要先向 RTC SDK 获取一个 SurfaceTexture 并初始化所需要的 Surface, Handler 然后通过上述流程获取到的 MediaProjection 对象创建一个 VirtualDisplay 对象,此时 RTC SDK 就能获取到屏幕采集视频帧数据了。

SurfaceTexture texture = ZegoCustomVideoCaptureManager.getInstance().getSurfaceTexture(0);
texture.setDefaultBufferSize(width, height);
Surface surface = new Surface(texture);
HandlerThread handlerThread = new HandlerThread("ZegoScreenCapture");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());

VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture", width, height, 1,
    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, handler);

本文为原创稿件,版权归作者所有,如需转载,请注明出处:https://www.nxrte.com/jishu/17806.html

(0)

相关推荐

  • Flutter iOS 实现屏幕采集

    在视频会议、线上课堂、游戏直播等场景,屏幕共享是一个最常见的功能。屏幕共享就是对屏幕画面的实时共享,端到端主要有几个步骤:录屏采集、视频编码及封装、实时传输、视频解封装及解码、视频…

    2022年4月9日

发表回复

登录后才能评论