Android 屏幕共享

目前 SDK 只支持每个 app 创建一个 RtcEngine 实例。如果你要同时发送屏幕共享和本地采集的视频,需要通过双进程实现。

原理如下:

实现方式

通过双进程同时发布屏幕共享流和摄像头采集流 是结合 Android 原生的屏幕采集 API 通过加入频道实现该功能。

注意:

本节示例代码涉及到 Android 的 MediaProjection 和 VirtualDisplay API,对 Android 版本和 API level 有以下要求: Android 版本是 Lollipop 及以上。 MediaProjection API 需要 Android API level 21 及以上。 VirtualDisplay API 需要 Android API level 19 及以上。 详细的使用方法和注意事项请参考 Android 官方文档 MediaProjection API 文档和 VirtualDisplay API 文档。

你需要分别为屏幕共享发流和摄像头采集发流创建独立的进程。

两个进程分别通过以下方法将视频数据发送给 SDK:

摄像头采集发流进程:通过 joinChannel 方式实现。此进程可作为主进程,并通过 AIDL (Android Interface Definition Language) 与屏幕共享发流进程进行通信。你可以参考 Android 官方文档了解 AIDL。 屏幕共享发流进程:屏幕共享功能通过 MediaProjection、VirtualDisplay 和自定义视频采集方式实现。屏幕共享进程会单独创建一个 RtcEngine 对象,并通过此对象创建和加入一个用于屏幕共享发流的频道。

注意:

两个进程的用户需要加入同一个频道。用户成功加入频道后,默认订阅频道内所有其他用户的音频流和视频流,因此产生用量并影响计费。因为屏幕共享发流进程的用户只用于发布屏幕共享流,本身无需订阅任何音视频流,你可以通过调用muteAllRemoteAudioStreams(true) 和 muteAllRemoteVideoStreams(true) 停止接收远端的音视频流。

示例代码:

下面的示例代码使用多进程发送屏幕共享和本地采集的视频。进程之间使用 AIDL 进行通信。

1.在 AndroidManifest.xml 中为组件设置 android:process

<activity
	android:name=".easeui.agora.impl.ScreenCapture$ScreenCaptureAssistantActivity"
	android:process=":screensharingsvc"
	android:screenOrientation="fullUser"
	android:theme="@android:style/Theme.Translucent" />

<service
	android:name=".easeui.agora.impl.ScreenSharingService"
  android:foregroundServiceType="mediaProjection"
  android:process=":screensharingsvc">
  <intent-filter>
		<action android:name="android.intent.action.screenshare" />
	</intent-filter>
</service>

2.创建 AIDL 接口,接口中包含用于进程间通信的方法

// 包含控制屏幕共享进程的方法
// IScreenSharing.aidl
package io.agora.rtc.ss.aidl;

import io.agora.rtc.ss.aidl.INotification;

interface IScreenSharing {
    void registerCallback(INotification callback);
    void unregisterCallback(INotification callback);
    void startShare();
    void stopShare();
    void renewToken(String token);
}


// 包含接收屏幕共享进程的回调
// INotification.aidl
package io.agora.rtc.ss.aidl;

interface INotification {
    void onError(int error);
    void onTokenWillExpire();
    void onDialogStart();
    void onDialogDeniedError(int error);
}

3.实现屏幕共享发流进程。屏幕共享功能通过 MediaProjection、VirtualDisplay 和自定义视频采集方式实现。屏幕共享进程会单独创建一个 RtcEngine 对象,并通过此对象创建和加入一个用于屏幕共享发流的频道。

// 定义 ScreenSharingClient 对象
public class ScreenSharingClient {
    private static final String TAG = ScreenSharingClient.class.getSimpleName();
    private static IScreenSharing mScreenShareSvc;
    private IStateListener mStateListener;
    private static volatile ScreenSharingClient mInstance;

    public static ScreenSharingClient getInstance() {
        if (mInstance == null) {
            synchronized (ScreenSharingClient.class) {
                if (mInstance == null) {
                    mInstance = new ScreenSharingClient();
                }
            }
        }

        return mInstance;
    }

// 开始屏幕共享
@TargetApi(21)
public void start(Context context, String appId, String token, String channelName, int uid, VideoEncoderConfiguration vec) {
        if (mScreenShareSvc == null) {
            Intent intent = new Intent(context, ScreenSharingService.class);
            intent.putExtra(Constant.APP_ID, appId);
            intent.putExtra(Constant.ACCESS_TOKEN, token);
            intent.putExtra(Constant.CHANNEL_NAME, channelName);
            intent.putExtra(Constant.UID, uid);
            intent.putExtra(Constant.WIDTH, vec.dimensions.width);
            intent.putExtra(Constant.HEIGHT, vec.dimensions.height);
            intent.putExtra(Constant.FRAME_RATE, vec.frameRate);
            intent.putExtra(Constant.BITRATE, vec.bitrate);
            intent.putExtra(Constant.ORIENTATION_MODE, vec.orientationMode.getValue());
            context.bindService(intent, mScreenShareConn, Context.BIND_AUTO_CREATE);
        } else {
            try {
                mScreenShareSvc.startShare();
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, Log.getStackTraceString(e));
            }
        }

    }

// 停止屏幕共享
@TargetApi(21)
public void stop(Context context) {
        if (mScreenShareSvc != null) {
            try {
                mScreenShareSvc.stopShare();
                mScreenShareSvc.unregisterCallback(mNotification);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, Log.getStackTraceString(e));
            } finally {
                mScreenShareSvc = null;
            }
        }
        context.unbindService(mScreenShareConn);
    }

...

}

bind 屏幕共享 service 之后,屏幕共享进程会创建一个 RtcEngine 对象,并加入用于屏幕共享发流的频道。

@Override
public IBinder onBind(Intent intent) {
    // 创建一个 RtcEngine 对象
    setUpEngine(intent);
    // 设置视频编码属性
    setUpVideoConfig(intent);
    // 加入频道
    joinChannel(intent);
    return mBinder;
}

屏幕共享进程创建 RtcEngine 对象时,需要进行以下设置:
// 取消订阅所有音频流
mRtcEngine.muteAllRemoteAudioStreams(true);
// 取消订阅所有视频流
mRtcEngine.muteAllRemoteVideoStreams(true);
// 关闭音频模块
mRtcEngine.disableAudio();

4.实现摄像头本地采集发流进程以及屏幕共享进程的触发机制,详细查看demo里的CallActivity类实现

开发注意事项: 屏幕共享的进程中,需要调用 muteAllRemoteVideoStreams 和 muteAllRemoteAudioStreams 方法取消接收远端用户的流,避免重复订阅。 详细使用:请参照demo、具体app,使用可以参考声网api。