本文介绍基于无影云手机与虚拟直播的自动化直播方案。
场景描述
云手机结合虚拟直播技术可构建高效的直播解决方案,依托云端算力实现虚拟形象的实时渲染、语音合成及交互响应。通过云手机调用预训练的虚拟主播模型,用户输入文本即可自动生成对应的语音与视频内容,配合直播脚本实现全天候自动化直播。除经典的数字人直播以外,也适用于其他的虚拟直播方案。
云手机支持弹性资源调度,保障直播期间高并发访问下的系统稳定性。针对运营人员有限的中小商家,该方案支持多账号矩阵的内容统一管理与批量更新,仅需1至2名运营人员即可完成日常维护,显著降低人力投入。同时,云手机集成推流能力,支持观众实时提问的AI语义解析与虚拟主播的动态反馈,结合预设的安全组策略与带宽控制机制,确保直播过程的流畅性与安全性。
场景特征
对算力要求高
随着直播类移动应用对计算资源需求的持续增加,其CPU占用率显著上升,现有主流个人终端的算力配置已难以满足此类场景的稳定运行需求。
对渲染要求高
直播平台通常对视频清晰度和流畅度具有较高要求,对云手机厂商的视频编解码效率及图形渲染能力提出了更高标准。
直播平台风控策略更新频繁
需由云手机厂商提供真机模拟能力,供虚拟直播厂商实施设备定制化配置。
部分直播类应用对 IP 地址有较高要求,例如部分电商应用可能针对BGP线路设置额外的风控策略。
解决方案

方案优势
Arm 服务器架构可满足直播类应用不断增长的算力需求,支持自定义矩阵开数以灵活分配算力资源,可根据实际应用场景按需配置,提升资源利用效率与部署灵活性。
采用存算分离架构,存储可选共享存储或独立存储模式,支持按需动态扩容,提升存储灵活性与资源利用率。
操作步骤
开通云手机矩阵
参考创建和管理云手机矩阵,购买cpm.gx8.16xlarge规格的云手机。
支持自定义开数,开数可通过矩阵改配进行后期调整。
矩阵改配将丢失矩阵内所有数据。
网络配置
云手机标准网络
无影云手机提供标准网络接入服务,支持BGP多线和静态单线两种网络类型。
标准网络默认配置为BGP线路,IP出口归属阿里云。为确保应用稳定运行,建议准备家庭宽带代理。
代理
如果需要家庭宽带代理,请提交工单联系技术团队完成相关配置。
真机模拟及 APP 运行环境准备
以下步骤仅需要在云手机创建后操作一次。
步骤一:真机模拟
部分直播平台需要验证机型参数,平台针对不同配置的机型存在分辨率、帧率等直播参数限制,针对以上场景需对云手机进行真机模拟。
如果真机模拟过程中遇到问题,请提交工单联系技术团队完成相关配置。
步骤二:一键换机
在云手机助手中完成一键换机。


步骤三:清除应用缓存
在云手机中找到直播应用,并清除应用缓存。

步骤四:确保切换至真机摄像头模式
远程命令执行如下命令,然后重启云机。
wy_virt_streaming stop
setprop persist.wy.camera.max.resolution 720p如果触发应用风控需要进行人脸识别:
请使用安卓客户端进行人脸识别。
需要在暗光环境下。
人脸需要尽可能靠近屏幕。
部署推流服务
可自行创建ECS实例并部署RTMP推流服务器。若推流服务器与云手机需通过内网通信,需确保ECS实例与CPS实例位于同一地域。如需实现内网互通,请联系技术支持。
ECS推流服务器启动推流时,请勿在FFmpeg命令中添加crf参数,示例如下:
ffmpeg -stream_loop -1 -re -i ./output_hw_1080p.mp4 -c copy -f flv rtmp://localhost/live/livestream确保 MP4 文件为横屏画面,使用 FFmpeg 检查并转换分辨率以适配横向格式,命令如下:
ffmpeg -i mp4。MP4 文件支持 720p 和 1080p 分辨率。
MP4 文件支持 30 fps 帧率。
若视频不符合上述要求,需使用 FFmpeg 预先转换视频格式,再通过转换后的视频进行推流。
使用云手机一键开播工具
在云手机中安装VLC应用,并验证视频流是否成功加载。
在云手机中执行
wy_virt_streaming命令。wy_virt_streaming start native -av rtmp://url打开相机应用后,设备将显示实时画面。若未正常显示画面,请关闭相机应用,等待5秒后重新启动。
结束虚拟直播后,执行以下命令切换至真机摄像头以继续使用。
wy_virt_streaming stop
云手机一键开播工具命令大全
wy_virt_streaming start mode [-a path] -v[path]
-a: 写入audio, 后面跟audio的文件路径或者rtmp server路径
-v: 写入vido, 后面跟video的文件路径或者rtmp server路径
-m: 镜像功能,-1开启, -0关闭,可选,一般默认即可
-r: 旋转功能,90/180/270,可选,一般默认即可
#通过java sdk注入音视频
wy_virt_streaming start java(需要集成javasdk或者安装demo app)
#通过无影通用直播方案注入音视频
wy_virt_streaming start native -av rtmp://1111(从rtmp流写入音视频)
wy_virt_streaming start native -v rtmp://1111 (只写入视频)
wy_virt_streaming start native -av /data/local/tmp/test.mp4(本地mp4文件)
wy_virt_streaming start native -v /data/local/tmp/test.mp4(从本地文件仅写入视频)
wy_virt_streaming stop(停止虚拟直播,恢复摄像头重定向模式)高阶使教程:自定义音视频注入
SDK 与 Javadoc 获取
下载虚拟摄像头 Java SDK。
该 SDK 目前处于邀测中,如需体验,请提交工单联系支持团队开通功能。
虚拟直播模式切换(仅供测试)
从摄像头重定向切换到虚拟摄像头(虚拟直播模式),需要设置如下属性值:
setprop config.wy.virtualLive 1
setprop persist.config.wy.camera.socket 2切换完成后,需要重启相关进程:
ps -elf | grep cameraserv | awk '{print $2}' | xargs kill -9
stop camera-client&&start camera-client引用 Java SDK
引用 SDK
虚拟摄像头 Java SDK 是一个已经打包好的 AAR 库,直接将其作为Implementation引用至安卓应用项目中即可调用虚拟摄像头。
导入 Javadoc(可选)
若需要在兼容的 IDE(如 Android Studio)中获取文档提示,可以导入随 SDK 提供的Javadoc包。对于 Android Studio,具体操作为:External Libraries -> libvhal_sdk-release.aar -> Library Properties -> Add Javadoc attachments,选择解压好的Javadoc即可。
类图(SDK 只包含 Java 部分)

基本调用流程
视频流
步骤一:构建摄像头 VideoSink
在产生和运送虚拟摄像头所消耗的视频内容前,需要首先构建
VideoSink以创建至虚拟摄像头 HAL 的连接。可以使用InstanceManager获取VideoSink实例。在应用生命周期内,任何时间只能同时有一个存活的
VideoSink实例。每次调用NewVideoSink方法均会导致旧有的实例遭到释放,并被替换为新创建的实例,因此需要格外注意,避免引用已经遭到释放的VideoSink实例。示例代码如下:
VideoSink videoSink = InstanceManager.getInstance().NewVideoSink("/conn", new IMediaCallback() { @Override public void onOpen() { } @Override public void onClose() { } @Override public void onServerUp() { } @Override public void onNone() { } });步骤二:根据需要获取和设置摄像头能力
虚拟摄像头当前支持四种分辨率的视频:480p、720p、1080p、1080p_t1,支持四种格式的编码:H264、H265、I420、AV1。为确保虚拟摄像头 HAL 理解传入的数据包格式,需要在首次运送数据包前调用
VideoSink::SetCameraCapability通告即将传入的数据包分辨率与格式。示例代码如下:
CameraInfo[] cameraInfos = new CameraInfo[2]; CameraInfo cameraInfo = new CameraInfo(); cameraInfo.cameraId = 0; cameraInfo.codecType = VideoCodecType.kI420; cameraInfo.facing = CameraFacing.BACK_FACING; cameraInfo.resolution = FrameResolution.k720p; cameraInfo.sensorOrientation = SensorOrientation.ORIENTATION_90; cameraInfos[0] = cameraInfo; cameraInfo = new CameraInfo(); cameraInfo.cameraId = 1; cameraInfo.codecType = VideoCodecType.kI420; cameraInfo.facing = CameraFacing.FRONT_FACING; cameraInfo.resolution = FrameResolution.k720p; cameraInfo.sensorOrientation = SensorOrientation.ORIENTATION_270; cameraInfos[1] = cameraInfo; videoSink.SetCameraCapability(cameraInfos);以上代码定义了两个虚拟摄像头。其中虚拟的后置摄像头支持 720p 分辨率,传入数据格式为 I420,传感器方向顺时针旋转 90 度。虚拟的前置摄像头支持 720p 分辨率,传入数据格式为 I420,传感器方向顺时针旋转 270 度。
如果不确定当前版本虚拟摄像头 HAL 支持哪些能力,可以调用
VideoSink::GetCameraCapability来获取其支持的能力。步骤三:生产和运送视频数据
自行控制帧传输速率
如果决定自行控制帧传输速率,则直接向
VideoSink运送 packet 即可。其中,packet 的数据格式为步骤二中设定的数据格式。具体调用方式如下所示。// VideoSink::SendDataPacket 方法有多个重载,根据需要选择合适的重载使用即可。 videoSink.SendDataPacket(bufferToSend);重要如果采用该方法运送数据,则调用者需要确保运送数据的频率与视频帧率相吻合,否则将导致视频异常地加快或变慢。
使用预设的 Consumer(仅 I420)
如果需要运送的数据格式为 I420,且调用方难以自行控制数据运送频率,则可以使用预设的
VideoStreamConsumer来运送数据。将视频流、视频宽高与帧率属性传入其构造函数后,即可调用BeginConsuming来流式传输数据。
音频流
步骤一:构建麦克风 AudioSink
与视频流类似,向虚拟麦克风 HAL 运送音频数据之前,需要构建
AudioSink实例以创建到麦克风 HAL 的连接。并使用InstanceManager来获取AudioSink实例。AudioSink audioSink = InstanceManager.getInstance().NewAudioSink("127.0.0.1", new IMediaCallback() { @Override public void onOpen() { } @Override public void onClose() { } @Override public void onServerUp() { } @Override public void onNone() { } });步骤二:生产和运送音频数据
自行控制传输速率
由于虚拟麦克风仅支持 48kHz、16位采样深度的双声道音频,因此无需像虚拟摄像头一样设置能力。
AudioSink构建完成后,即可立即用于音频数据运送。示例如下。audioSink.SendDataPacket(audioBuffer);重要如果采用该方法运送数据,则一个数据包应当包含 10ms 的音频数据(1920字节),且每秒应当均匀地传送 100 个数据包。不均匀或过慢的速率将导致音频卡顿。
使用预设的 Consumer
与视频数据类似,如果难以自行控制音频数据传输速率,则可以使用预定义的
AudioStreamConsumer来运送数据。将音频属性传入其构造函数后,调用BeginConsuming来流式传输数据。
底层资源释放
虚拟摄像头/麦克风 Java SDK 是对底层 Socket 连接(Unix Domain Socket(视频)/TCP Socket (音频))与底层 native 客户端库的抽象封装,一个 AudioSink/VideoSink Java 对象均对应一个 native 层对象,与一个 Socket 连接。
AudioSink与VideoSink均实现AutoClosable,可以手动调用Close方法释放其对应的底层资源,或使用try-with-resource语法自动释放。不过,鉴于通常 AudioSink 与 VideoSink 需要维持与业务周期相等的长生命周期,这取决于开发者选择何时释放底层资源。
如果忘记调用Close方法,对象的 finalizer 仍将在 Java 对象被回收时释放底层资源。