火山引擎云手机支持虚拟摄像头、虚拟麦克风功能,能够实现将真机摄像头和麦克风采集到的音视频数据注入到云机实例的虚拟摄像头/麦克风中,供实例上运行的应用获取和使用。
本文介绍如何使用火山引擎云手机客户端 SDK 内部采集功能,完成将本地采集的音视频数据注入到云机实例的过程。本功能适用于扫一扫、云机真人直播、云机人脸识别等应用场景。外部音视频数据注入功能请参考客户端外部源注入;音视频裸数据注入功能请参考音视频裸数据注入(Pod 注入)。
已集成火山引擎云手机客户端 SDK:
Android:参考 Android SDK 集成指南,集成 1.20.0+ 版本的 SDK。
Web:参考 Web/H5 SDK 集成指南,集成 1.28.2+ 版本的 SDK。
具备加强型及以上配置的实例资源。不同的应用程序对云手机性能配置的需求各异,涉及人脸识别功能的应用,建议使用加强型及以上规格的设备,以避免因性能不足导致的识别失败等问题。
需要音视频注入的应用程序已在控制台完成应用加签(适用于自主开发的应用,第三方应用无需关注)。
内部采集视频注入只支持竖屏模式下使用,横屏模式下会出现显示异常等不兼容情况。
该功能仅适用于基础的识别场景,对于需要高度安全性的支付级别应用所使用的人脸识别,目前暂不提供支持。若将该功能用于高安全级别人脸识别,应自行评估并承担相关风险。
当前云手机 SDK 的音视频内部采集功能专为手机设备设计,对于非手机环境(例如基于 Android 系统的车载信息娱乐系统)支持不完善。针对此类情况,建议参考客户端外部源注入使用 SDK 提供的外部采集接口,自行采集音视频并推送至云手机。若在车机场景中上传摄像头采集数据,应自行评估并承担相关风险。
三种音视频注入模式存在优先级和切换限制:
本功能与客户端外部源音视频注入、音视频裸数据注入(Pod 注入)功能互斥,即云机实例同一时间只能接收一种注入源;
当三种模式并存时,云机实例接收注入源的优先级为:客户端外部源音视频注入>音视频裸数据注入(Pod 注入)>客户端内部源音视频注入;
若切换至其他注入源,请务必先关闭摄像头和麦克风,关闭当前正在注入的应用程序,否则会导致切换失败。

第一个进房的客户端默认拥有音视频注入权限。若后续存在多个客户端进房、且有音视频注入需求的情况,则后接入的客户端需通过特定的接口调用来获取注入控制权。
注意:同一时间内有且仅有一个客户端可持有向云机实例注入音视频的权限。
public interface CameraManager { /** * 开启/关闭视频注入功能 * * @param state 视频注入状态 true:开启 false:关闭 * @return 0:调用成功 -1:引擎未初始化 */ int setVideoInjectionState(boolean state); /** * 获取视频注入状态设置 * * @return 0:调用成功 -1:引擎未初始化 */ int getVideoInjectionState(); /** * 设置视频注入功能相关回调 * * @param listener 回调 */ void setVideoInjectionListener(VideoInjectionListener listener); }
通过注册视频注入功能相关回调,监听视频注入权限变更:
interface VideoInjectionListener { /** * 视频注入状态改变的回调 * * @param callUserId 改变视频注入状态的用户 ID * @param state 视频注入状态 true:开启 false:关闭 * @param code 错误码 0:成功 <0:失败 * @param msg 错误信息 */ void onVideoInjectionStateChanged(String callUserId, boolean state, int code, String msg); /** * 获取视频注入状态的结果回调 * * @param state 视频注入状态 true:开启 false:关闭 * @param code 错误码 0:成功 <0:失败 * @param msg 错误信息 */ void onGetVideoInjectionState(boolean state, int code, String msg); }
public interface AudioService { /** * 设置音频注入状态(用于云手机场景) * * @param state 音频注入状态 true:开启 false:关闭 * @return 0:调用成功 -1:DataChannel为空 */ int setAudioInjectionState(boolean state); /** * 获取音频注入状态(用于云手机场景) * * @return 0:调用成功 -1:DataChannel为空 */ int getAudioInjectionState(); /** * 设置音频注入状态监听器(用于云手机场景) * * @param listener 音频注入状态监听器 */ void setAudioInjectionListener(AudioInjectionListener listener); }
通过注册音频注入功能相关回调,监听音频注入权限变更:
interface AudioInjectionListener { /** * 音频注入状态改变的回调 * * @param callUserId 改变音频注入状态的用户ID * @param state 音频注入状态 true:开启 false:关闭 * @param code 错误码 0:成功 <0:失败 * @param msg 错误信息 */ void onAudioInjectionStateChanged(String callUserId, boolean state, int code, String msg); /** * 获取音频注入状态的结果回调 * * @param state 音频注入状态 true:开启 false:关闭 * @param code 错误码 0:成功 <0:失败 * @param msg 错误信息 */ void onGetAudioInjectionState(boolean state, int code, String msg); }
当云机实例打开相机等请求摄像头的应用时,客户端可以通过 onVideoStreamStartRequested 回调监听开始视频采集请求,然后调用 startVideoStream 接口向云机推送本地摄像头采集到的视频流。当云机无需视频采集时,客户端也可以通过回调监听停止视频采集请求,然后调用 stopVideoStream 接口停止向云机推送视频流。
public interface CameraManager { /** * 切换摄像头 * @param cameraId FRONT(0):前置摄像头; BACK(1):后置摄像头。暂不支持外接摄像头。 * @return 0:成功,其他失败 */ int switchCamera(CameraId cameraId); /** * 设置本地视频编码配置 * @param descList */ void setVideoEncoderConfig(List<LocalVideoStreamDescription> descList); /** * 开始推送Camera视频流 * @param cameraId * @return 0:成功,其他失败 */ int startVideoStream(CameraId cameraId); /** * 停止推送Camera视频流 */ int stopVideoStream(); /** * 设置远端摄像机流请求监听 * @param listener */ void setRemoteRequestListener(RemoteCameraRequestListener listener); }
监听云机请求本地摄像头采集的开启和关闭。
public interface RemoteCameraRequestListener { void onVideoStreamStartRequested(CameraId cameraId); void onVideoStreamStopRequested(); }
与视频采集类似,当云机实例打开录音机等请求麦克风的应用时,客户端可以通过 onRemoteAudioStartRequest 回调监听开始音频采集请求,然后调用 startSendAudioStream 接口向云机推送本地麦克风采集到的音频数据。当云机无需音频采集时,客户端也可以通过回调监听停止音频采集请求,然后调用 stopSendAudioStream 接口停止向云机推送音频数据。
public interface AudioService { /** * 获取本地音频采集功能启用状态 * * @return true:已启用;false:已禁用 */ boolean isEnableSendAudioStream(); /** * 设置是否启用本地音频采集功能 * * @param enable true:启用;false:禁用 */ void setEnableSendAudioStream(boolean enable); /** * 开始采集音频并向云机实例发送 * @return 0:方法调用成功;-1:方法调用失败 */ @RequiresPermission(Manifest.permission.RECORD_AUDIO) int startSendAudioStream(); /** * 停止音频采集和发送 * @return 0:方法调用成功 -1 方法调用失败 */ int stopSendAudioStream(); /** * 是否正在进行音频上报 * @return true-上报中;false-未上报 */ boolean isSendingAudioStream(); /** * 设置本地音频采集音量 * @param volume [0, 100] * @return 0:方法调用成功;-1:方法调用失败 */ int setLocalAudioCaptureVolume(@IntRange(from = 0, to = 400) int volume); /** * 获取本地音频采集音量 * * @return volume [0, 100] */ @IntRange(from = 0, to = 100) int getLocalAudioCaptureVolume(); void setAudioControlListener(AudioControlListener listener); }
监听云机请求本地麦克风采集的开启和关闭。
interface AudioControlListener { /** * pod请求开启本地音频推流 */ void onRemoteAudioStartRequest(); /** * pod请求关闭本地音频推流 */ void onRemoteAudioStopRequest(); /** * 本地音频播放设备变更 * * @see AudioService#setAudioPlaybackDevice(int) */ void onAudioPlaybackDeviceChanged(@AudioPlaybackDevice int device); /** * 本地音频设备状态和错误码 */ void onLocalAudioStateChanged(LocalAudioStreamState state, LocalAudioStreamError error); }