Files
SellyCloudSDK_Android_demo/docs/SellySDK_音视频通话接入文档_Android.md

16 KiB
Raw Blame History

SellyRTC Android SDK 接入文档

本文档用于指导 Android App 开发者 快速接入 SellyRTC完成一对一或多人实时音视频通话能力。

SDK 核心以 InteractiveRtcEngine 为中心,通过 InteractiveRtcEngineEventHandler 回调通话状态、用户事件、音视频状态及异常。

当前版本的互动渲染模型已经从“仅 SurfaceViewRenderer”扩展为“RtcRenderTarget 抽象 + 多种后端实现”:

  • SurfaceViewRenderer 旧路径仍可用
  • TextureView 已可用于本地/远端视频渲染
  • 推荐在 加入频道前 选定本地渲染后端

目录

  1. 准备工作
  2. 快速开始
  3. 基础通话流程
  4. 常用功能
  5. 屏幕分享
  6. 视频帧前后处理
  7. 事件回调EventHandler
  8. 通话统计
  9. Token 机制
  10. 代理地址配置
  11. 常见问题FAQ

准备工作

1. 环境要求

  • Android 8.0+Demo minSdk 为 26
  • 需真机运行(摄像头 / 麦克风)

2. 权限配置AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

快速开始

1. SDK 初始化

在使用音视频通话功能前,需先初始化 SDK

SellyCloudManager.initialize(
    context = applicationContext,
    appId = "your-app-id"
)

initializeappId 参数为权威值。可选传入 SellyCloudConfig 配置 vhostlogEnabled 等,详见推拉流文档。

2. 代理地址设置(可选)

若需通过代理(如洋葱盾)连接信令服务器,在创建引擎前设置:

SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")

SDK 内部通过代理地址解析真实信令服务器 IP。不设置则使用直连。详见「代理地址配置」章节。

3. 创建引擎

val appId = getString(R.string.signaling_app_id)
val token = getString(R.string.signaling_token).takeIf { it.isNotBlank() }

val rtcEngine = InteractiveRtcEngine.create(
    InteractiveRtcEngineConfig(
        context = applicationContext,
        appId = appId,
        defaultToken = token
    )
).apply {
    setEventHandler(eventHandler)
    setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
    setVideoEncoderConfiguration(
        InteractiveVideoEncoderConfig(
            width = 640,
            height = 480,
            fps = 20,
            minBitrateKbps = 150,
            maxBitrateKbps = 850
        )
    )
    setDefaultAudioRoutetoSpeakerphone(true)
}

InteractiveRtcEngineConfig 参数说明:

参数 类型 说明
context Context 应用上下文
appId String 应用 ID
defaultCallType CallType 默认通话类型,默认 ONE_TO_ONE
defaultToken String? 默认 Token
signalingUrlPrefix String 信令 URL 前缀,默认 ws://
signalingUrlSuffix String 信令 URL 后缀,默认 /ws/signaling

完整 Demo 见 example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt

4. 设置本地/远端画布

推荐使用 InteractiveVideoCanvas(renderTarget, userId) 新接口。

4.1 SurfaceViewRenderer 旧路径

val localRenderer = SurfaceViewRenderer(this)
val localCanvas = InteractiveVideoCanvas(
    com.sellycloud.sellycloudsdk.render.SurfaceViewRtcTarget(localRenderer),
    userId
)
rtcEngine.setupLocalVideo(localCanvas)
val remoteRenderer = SurfaceViewRenderer(this)
val remoteCanvas = InteractiveVideoCanvas(
    com.sellycloud.sellycloudsdk.render.SurfaceViewRtcTarget(remoteRenderer),
    remoteUserId
)
rtcEngine.setupRemoteVideo(remoteCanvas)

4.2 TextureView 路径

val localTextureView = com.sellycloud.sellycloudsdk.widget.AspectRatioTextureView(this)
val localCanvas = InteractiveVideoCanvas(
    com.sellycloud.sellycloudsdk.render.TextureViewRtcTarget(localTextureView),
    userId
)
rtcEngine.setupLocalVideo(localCanvas)
val remoteTextureView = com.sellycloud.sellycloudsdk.widget.AspectRatioTextureView(this)
val remoteCanvas = InteractiveVideoCanvas(
    com.sellycloud.sellycloudsdk.render.TextureViewRtcTarget(remoteTextureView),
    remoteUserId
)
rtcEngine.setupRemoteVideo(remoteCanvas)

兼容说明:

  • InteractiveVideoCanvas(view: SurfaceViewRenderer, userId) 旧构造仍可用deprecated
  • 推荐新接入统一走 RtcRenderTarget
  • 当前高层互动 API 还没有直接暴露 SurfaceTexture 入口Android 场景推荐 SurfaceViewRendererTextureView

所有权说明:

  • 调用方自己创建的 SurfaceViewRenderer / TextureView,由调用方负责释放
  • SDK 只在 setupLocalVideo / setupRemoteVideo 中绑定 targetleaveChannel 时解绑
  • 调用方应在 leaveChannel 之后、Activity 销毁前释放自己创建的 View

5. 加入通话

val options = InteractiveChannelMediaOptions(callType = CallType.ONE_TO_ONE)
rtcEngine.joinChannel(
    token = token,
    channel = callId,
    userId = userId,
    options = options,
    tokenSecret = tokenSecret,
    tokenExpiresAtSec = tokenExpiresAtSec,
    tokenTtlSeconds = tokenTtlSeconds
)

结束通话:

rtcEngine.leaveChannel()

销毁引擎:

InteractiveRtcEngine.destroy(rtcEngine)

6. 进阶配置Demo 未覆盖)

6.1 InteractiveChannelMediaOptions 订阅控制

val options = InteractiveChannelMediaOptions(
    callType = CallType.GROUP,
    autoSubscribeAudio = true,
    autoSubscribeVideo = false
)

6.2 InteractiveVideoEncoderConfig 更多参数

可选项(按需设置):

  • codecType:编码类型
  • bitrateMode:码率控制模式
  • minBitrateKbps / maxBitrateKbps:码率范围
  • orientationMode:画面方向策略
  • degradationPreference:弱网降级策略
  • mirrorMode:镜像策略

基础通话流程

  1. 初始化 SDKSellyCloudManager.initialize
  2. 设置代理地址(可选,SellyCloudManager.setProxyAddress
  3. 创建 InteractiveRtcEngine
  4. 设置 EventHandler
  5. 配置 InteractiveVideoEncoderConfig
  6. 设置本地画布 setupLocalVideo(建议在 joinChannel 前完成,并在此阶段确定 backend
  7. joinChannel 加入频道
  8. onUserJoined 后设置远端画布;也可以提前为某个 userId 调用 setupRemoteVideoSDK 会在用户真正上线后自动 attach
  9. 通话中进行音视频控制
  10. leaveChannel 并释放资源

常用功能

本地音视频控制

rtcEngine.enableLocalVideo(true)
rtcEngine.enableLocalAudio(false)

切换摄像头

rtcEngine.switchCamera()

音频输出(扬声器 / 听筒)

rtcEngine.setDefaultAudioRoutetoSpeakerphone(true)

发送自定义消息

rtcEngine.sendMessage("hello") { error ->
    // error == null 表示成功
}

按用户静音远端音频/视频

rtcEngine.muteRemoteAudioStream(remoteUserId, true)
rtcEngine.muteRemoteVideoStream(remoteUserId, true)

全量静音远端音视频Demo 未覆盖)

rtcEngine.muteAllRemoteAudioStreams(true)
rtcEngine.muteAllRemoteVideoStreams(true)

清理远端画布Demo 未覆盖)

rtcEngine.clearRemoteVideo(remoteUserId)

向指定用户发送消息Demo 未覆盖)

rtcEngine.sendMessage("hello", targetUserId) { error ->
    // error == null 表示成功
}

屏幕分享

val started = rtcEngine.startScreenShare(
    resultCode = resultCode,
    data = data,
    width = 720,
    height = 1280,
    fps = 15
)
rtcEngine.stopScreenShare()
val isSharing = rtcEngine.isScreenSharing()

Android 14+ 屏幕捕获需要前台服务Demo 使用 InteractiveForegroundService 处理)。


视频帧前后处理

rtcEngine.setCaptureVideoProcessor(object : VideoProcessor {
    override val config = VideoProcessorConfig(
        preferredFormat = VideoProcessFormat.TEXTURE_2D,
        mode = VideoProcessMode.READ_WRITE,
        fullRewrite = true
    )

    override fun processTexture(input: VideoTextureFrame, outputTextureId: Int) {
        // 推荐在 GPU texture 上处理采集前帧,美颜/滤镜直接写入 outputTextureId
    }
})
val renderObserver = rtcEngine.addRenderVideoFrameObserver(object : VideoFrameObserver {
    override val config = VideoFrameObserverConfig(
        preferredFormat = VideoProcessFormat.TEXTURE_2D,
        stage = VideoStage.RENDER_PRE_DISPLAY
    )

    override fun onTextureFrame(frame: VideoTextureFrame) {
        // 远端渲染前只读观测
        val userId = frame.sourceId
    }
})

推荐优先使用 TEXTURE_2D

  • TEXTURE_2D 适合美颜、滤镜、AR、水印等 GPU 处理链路。
  • I420 / RGBA 仅在算法必须访问 CPU 像素时再使用。
  • 对 RTC / WHIP 的 texture-backed 帧,走 CPU observer / processor 会触发额外的 texture-to-CPU 转换。
  • VideoFrameObserverConfig 默认仍为 I420 以兼容旧接入;新 RTC / WHIP 接入建议显式写 preferredFormat = TEXTURE_2D
  • 完整重写输出的处理器建议设置 fullRewrite = true;水印/叠加类处理保留默认值即可。

Demo 中的采集前美颜示例见: example/src/main/java/com/demo/SellyCloudSDK/beauty/FaceUnityBeautyEngine.kt

当前 Demo 的互动页接入见: example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt


事件回调EventHandler

val eventHandler = object : InteractiveRtcEngineEventHandler {
    override fun onJoinChannelSuccess(channel: String, userId: String, code: Int) {}
    override fun onLeaveChannel(durationSeconds: Int) {}
    override fun onUserJoined(userId: String, code: Int) {}
    override fun onUserLeave(userId: String, code: Int) {}
    override fun onConnectionStateChanged(state: InteractiveConnectionState, reason: Int, userId: String?) {}
    override fun onMessageReceived(message: String, userId: String?) {}
    override fun onError(code: String, message: String) {}
    override fun onTokenWillExpire(token: String?, expiresAt: Long) {}
    override fun onTokenExpired(token: String?, expiresAt: Long) {}
    override fun onLocalVideoStats(stats: InteractiveStreamStats) {}
    override fun onRemoteVideoStats(stats: InteractiveStreamStats) {}
    override fun onDuration(durationSeconds: Long) {}
    override fun onRemoteVideoEnabled(enabled: Boolean, userId: String?) {}
    override fun onRemoteAudioEnabled(enabled: Boolean, userId: String?) {}
    override fun onStreamStateChanged(peerId: String, state: RemoteState, code: Int, message: String?) {}
}

通话统计

InteractiveStreamStats 常用字段:

  • width / height
  • fps
  • videoBitrateKbps / audioBitrateKbps
  • rttMs
  • videoCodec / audioCodec

Token 机制

Token 来源

  • 业务侧服务端生成 Token 并下发
  • Demo 可在 strings.xml 配置 signaling_tokensignaling_secret 进行本地生成

Token 过期处理

  • onTokenWillExpire / onTokenExpired 回调提示即将过期或已过期
  • 建议获取新 Token 并 重新 joinChannel

Token 续期接口Demo 未覆盖):

rtcEngine.renewToken(newToken, expiresAtSec)

代理地址配置

SDK 支持通过外部代理(如洋葱盾等安全加速服务)连接信令服务器。代理地址由业务方在 SDK 外部获取,然后注入 SDK。

设置方式

// 设置代理地址(在 joinChannel 之前)
SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")

// 清除代理(恢复直连)
SellyCloudManager.setProxyAddress(null)

// 查询当前代理地址
val proxy = SellyCloudManager.getProxyAddress()  // null 表示未设置

格式要求

  • 必须以 http://https:// 开头
  • null 或空字符串表示清除代理
  • 格式不合法时抛出 IllegalArgumentException

生效范围

设置后SDK 内部通过代理地址解析真实信令服务器 IP对上层接口透明。

时机要求

  • 必须在 joinChannel() 之前 设置
  • 通话过程中修改代理地址,需 leaveChannel 后重新 joinChannel 才能生效

Demo 中的接入示例

Demo 使用 KiwiHelper 封装洋葱盾的初始化与代理获取,采用三阶段模式:

// 阶段 1Application.onCreate() 异步初始化
KiwiHelper.initializeAsync()

// 阶段 2Activity 初始化时启动代理获取(非阻塞)
KiwiHelper.startProxySetup(enableKiwi = true, rsName = "your-rs-name")

// 阶段 3joinChannel 前确保代理已就绪
lifecycleScope.launch {
    KiwiHelper.awaitProxyReady()
    rtcEngine.joinChannel(...)
}

KiwiHelper 内部通过 SellyCloudManager.setProxyAddress() 将代理地址传给 SDK。 详见 example/src/main/java/com/demo/SellyCloudSDK/KiwiHelper.kt


更多 API 速览(含 Demo 未覆盖)

引擎创建与销毁:

  • InteractiveRtcEngine.create(config):创建引擎
  • InteractiveRtcEngine.destroy(engine) / engine.destroy():释放引擎

SDK 初始化与代理:

  • SellyCloudManager.initialize(context, appId, config):初始化 SDK
  • SellyCloudManager.setProxyAddress(address):设置代理地址
  • SellyCloudManager.getProxyAddress():获取当前代理地址

通话控制:

  • setEventHandler(handler):设置事件回调
  • setClientRole(role):设置角色(主播/观众)
  • setVideoEncoderConfiguration(config):设置编码参数
  • setDefaultAudioRoutetoSpeakerphone(true/false):设置音频输出
  • joinChannel(...) / leaveChannel():加入 / 离开频道

本地与远端控制:

  • setupLocalVideo(canvas) / setupRemoteVideo(canvas):设置画布
  • InteractiveVideoCanvas(renderTarget, userId, renderMode):推荐画布模型
  • clearRemoteVideo(userId):清理远端画面
  • enableLocalVideo(true/false) / enableLocalAudio(true/false):开关本地音视频
  • muteRemoteAudioStream(userId, true/false) / muteRemoteVideoStream(userId, true/false):按用户静音
  • muteAllRemoteAudioStreams(true/false) / muteAllRemoteVideoStreams(true/false):全量静音
  • switchCamera():切换摄像头

帧处理与屏幕共享:

  • setCaptureVideoProcessor(...):采集前可写处理
  • addCaptureVideoFrameObserver(...):采集前只读观测
  • addRenderVideoFrameObserver(...):远端渲染前只读观测
  • startScreenShare(...) / stopScreenShare() / isScreenSharing():屏幕共享

消息与 Token

  • sendMessage(message, targetUserId, callback):发送消息(targetUserId 可为空)
  • renewToken(newToken, expiresAtSec):续期 Token

常见问题FAQ

Q远端画面不显示

  1. 是否设置了正确的 remoteUserId
  2. 是否在 onUserJoined 后调用 setupRemoteVideo
  3. 远端是否关闭了视频

Q互动直播可以用 TextureView 吗?

可以。

推荐用法是:

  • 本地:InteractiveVideoCanvas(TextureViewRtcTarget(textureView), userId)
  • 远端:InteractiveVideoCanvas(TextureViewRtcTarget(textureView), remoteUserId)

注意:

  • 建议在 joinChannel 前确定本地 backend
  • 当前 Demo 在首页设置中统一选择本地 backend进入互动页面后不再暴露切换入口
  • 高层互动 API 当前未直接暴露 SurfaceTexture 入口

Q加入频道失败

  1. 检查 signaling_app_id 是否正确
  2. Token 是否为空或已过期
  3. 网络是否受限
  4. 若使用代理,检查代理地址是否已正确设置

Q屏幕分享失败

  1. 是否已获取 MediaProjection 授权
  2. Android 14+ 是否启动前台服务

Q互动通话支持 XOR 吗?

当前高层互动 API 还没有暴露 xorKeyHex 一类的配置入口。

  • 目前已支持 XOR 的 WebRTC 路径,是直播 RTC 的 WHIP / WHEP 推拉流
  • 互动通话如需接入 XOR需要后续在互动链路单独暴露配置并挂载 FrameCrypto

Q如何接入代理/加速服务?

SDK 本身不集成任何第三方代理 SDK。业务方需在外部完成代理初始化获取本地代理地址后通过 SellyCloudManager.setProxyAddress() 注入。详见「代理地址配置」章节。