IVS 广播 SDK:混合设备
混合设备是音频和视频设备,采用多个输入源并生成单个输出。混合设备是一项强大的功能,可让您定义和管理多个屏幕上的(视频)元素和音轨。您可以组合来自多个来源的视频和音频,例如摄像头、麦克风、屏幕截图以及应用程序生成的音频和视频。您可以使用转换围绕流式传输到 IVS 的视频移动这些源,然后在流中添加和移除源。
混合设备分为图像和音频两种。要创建一个混合图像设备,请调用:
Android 上的 DeviceDiscovery.createMixedImageDevice()
iOS 上的 IVSDeviceDiscovery.createMixedImageDevice()
返回的设备可以附加到 BroadcastSession
(低延迟流式传输)或 Stage
(实时流式传输),就像任何其他设备一样。
术语

租期 | 描述 |
---|---|
设备 |
一种硬件或软件组件,用于生成到音频或图像输入。例如,设备包括麦克风、摄像头、蓝牙耳机和屏幕截图或自定义图像输入等虚拟设备。 |
混合设备 |
可以像其他任何 混合设备分为音频或图像两种。 |
混合设备配置 |
混合设备的配置对象。对于混合图像设备,这将配置诸如尺寸和帧率之类的属性。对于混合音频设备,这将配置通道计数。 |
来源 |
定义视觉元素在屏幕上的位置以及音频混合中音轨属性的容器。可以使用零个或多个源配置混合设备。向源提供了影响源媒体使用方式的配置。上图显示了四个图像源:
|
源配置 |
进入混合设备的源的配置对象。完整的配置对象如下所述。 |
Transition |
要将插槽移动到新位置或更改其部分属性,请使用
|
混合音频设备
配置
Android 上的 MixedAudioDeviceConfiguration
iOS 上的 IVSMixedAudioDeviceConfiguration
名称 | 类型 | 描述 |
---|---|---|
|
整数 |
音频混合器的输出通道数。有效值:1、2。1 为单声道音频;2 为立体声音频。默认值:2。 |
源配置
Android 上的 MixedAudioDeviceSourceConfiguration
iOS 上的 IVSMixedAudioDeviceSourceConfiguration
名称 | 类型 | 描述 |
---|---|---|
|
浮点型 |
音频增益。这是一个乘数,因此任何高于 1 的值都会提高增益;任何低于 1 的值都会减小增益。有效值:0-2。默认值:1。 |
混合图像设备
配置
Android 上的 MixedImageDeviceConfiguration
iOS 上的 IVSMixedImageDeviceConfiguration
名称 | 类型 | 描述 |
---|---|---|
|
Vec2 |
视频画布的大小。 |
|
整数 |
混合设备的每秒目标帧数。通常情况下,该值应能够得到满足,但是在某些情况下(例如 CPU 或 GPU 负载过高),系统可能会丢弃帧。 |
|
布尔值 |
这允许使用图像源配置上的 |
源配置
Android 上的 MixedImageDeviceSourceConfiguration
iOS 上的 IVSMixedImageDeviceSourceConfiguration
名称 | 类型 | 描述 |
---|---|---|
|
浮点型 |
插槽的 Alpha。这是与图像中的任何 Alpha 值相乘的结果。有效值:0-1。0 表示完全透明,1 表示完全不透明。默认值:1。 |
|
AspectMode | 适用于插槽中渲染的任何图像的宽高比模式。有效值:
默认值: |
|
Vec4 |
当插槽和图像的宽高比不匹配时,填充用于 |
|
Vec2 |
插槽相对于画布左上角的位置(以像素为单位)。插槽的原点也在左上角。 |
|
Vec2 |
插槽的大小(以像素为单位)。设置此值也会将 |
|
浮点型 |
插槽的相对顺序。 |
创建和配置混合图像设备

在这里,我们创建了一个类似于本指南开头的场景,其中包含三个屏幕元素:
-
左下角的摄像头插槽。
-
右下角的徽标叠加插槽。
-
右上角用于电影的插槽。
请注意,画布的原点是左上角,插槽也是一样。因此,将插槽定位在 (0, 0) 会将其放在左上角,且整个插槽可见。
iOS
let deviceDiscovery = IVSDeviceDiscovery() let mixedImageConfig = IVSMixedImageDeviceConfiguration() mixedImageConfig.size = CGSize(width: 1280, height: 720) try mixedImageConfig.setTargetFramerate(60) mixedImageConfig.isTransparencyEnabled = true let mixedImageDevice = deviceDiscovery.createMixedImageDevice(with: mixedImageConfig) // Bottom Left let cameraConfig = IVSMixedImageDeviceSourceConfiguration() cameraConfig.size = CGSize(width: 320, height: 180) cameraConfig.position = CGPoint(x: 20, y: mixedImageConfig.size.height - cameraConfig.size.height - 20) cameraConfig.zIndex = 2 let camera = deviceDiscovery.listLocalDevices().first(where: { $0 is IVSCamera }) as? IVSCamera let cameraSource = IVSMixedImageDeviceSource(configuration: cameraConfig, device: camera) mixedImageDevice.add(cameraSource) // Top Right let streamConfig = IVSMixedImageDeviceSourceConfiguration() streamConfig.size = CGSize(width: 640, height: 320) streamConfig.position = CGPoint(x: mixedImageConfig.size.width - streamConfig.size.width - 20, y: 20) streamConfig.zIndex = 1 let streamDevice = deviceDiscovery.createImageSource(withName: "stream") let streamSource = IVSMixedImageDeviceSource(configuration: streamConfig, device: streamDevice) mixedImageDevice.add(streamSource) // Bottom Right let logoConfig = IVSMixedImageDeviceSourceConfiguration() logoConfig.size = CGSize(width: 320, height: 180) logoConfig.position = CGPoint(x: mixedImageConfig.size.width - logoConfig.size.width - 20, y: mixedImageConfig.size.height - logoConfig.size.height - 20) logoConfig.zIndex = 3 let logoDevice = deviceDiscovery.createImageSource(withName: "logo") let logoSource = IVSMixedImageDeviceSource(configuration: logoConfig, device: logoDevice) mixedImageDevice.add(logoSource)
Android
val deviceDiscovery = DeviceDiscovery(this /* context */) val mixedImageConfig = MixedImageDeviceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(1280f, 720f)) setTargetFramerate(60) setEnableTransparency(true) } val mixedImageDevice = deviceDiscovery.createMixedImageDevice(mixedImageConfig) // Bottom Left val cameraConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(320f, 180f)) setPosition(BroadcastConfiguration.Vec2(20f, mixedImageConfig.size.y - size.y - 20)) setZIndex(2) } val camera = deviceDiscovery.listLocalDevices().firstNotNullOf { it as? CameraSource } val cameraSource = MixedImageDeviceSource(cameraConfig, camera) mixedImageDevice.addSource(cameraSource) // Top Right val streamConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(640f, 320f)) setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, 20f)) setZIndex(1) } val streamDevice = deviceDiscovery.createImageInputSource(streamConfig.size) val streamSource = MixedImageDeviceSource(streamConfig, streamDevice) mixedImageDevice.addSource(streamSource) // Bottom Right val logoConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(320f, 180f)) setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, mixedImageConfig.size.y - size.y - 20)) setZIndex(1) } val logoDevice = deviceDiscovery.createImageInputSource(logoConfig.size) val logoSource = MixedImageDeviceSource(logoConfig, logoDevice) mixedImageDevice.addSource(logoSource)
移除源
要移除源,请使用要移除的 Source
对象调用 MixedDevice.remove
。
动画转换
转换方法用新配置替换源的配置。通过将持续时间设置为大于 0(以秒为单位),可以将此替换制作为随时间推移的动画。
哪些属性可以制作成动画?
插槽结构中并非所有属性都可以制作成动画。基于 Float 类型的任何属性都可以制作成动画;其他属性在动画的开始或结束时生效。
名称 | 可以制作成动画吗? | 影响点 |
---|---|---|
|
是 |
已插入 |
|
是 |
已插入 |
|
否 |
结束 |
|
是 |
已插入 |
|
是 |
已插入 |
|
是 |
已插入 |
注意: |
是 |
未知 |
简单示例
以下是使用上述创建和配置混合图像设备中定义的配置进行全屏摄像头接管的示例。将其在 0.5 秒以内的变化制作成动画。
iOS
// Continuing the example from above, modifying the existing cameraConfig object. cameraConfig.size = CGSize(width: 1280, height: 720) cameraConfig.position = CGPoint.zero cameraSource.transition(to: cameraConfig, duration: 0.5) { completed in if completed { print("Animation completed") } else { print("Animation interrupted") } }
Android
// Continuing the example from above, modifying the existing cameraConfig object. cameraConfig.setSize(BroadcastConfiguration.Vec2(1280f, 720f)) cameraConfig.setPosition(BroadcastConfiguration.Vec2(0f, 0f)) cameraSource.transitionToConfiguration(cameraConfig, 500) { completed -> if (completed) { print("Animation completed") } else { print("Animation interrupted") } }
镜像广播
要在此方向镜像广播中附加的映像设备... | 使用负值表示... |
---|---|
水平 |
插槽的宽度 |
垂直 |
插槽的高度 |
水平和垂直方向 |
槽的宽度和高度 |
需要按相同的值调整位置,以便在镜像时将槽置于正确的位置。
以下是水平和垂直镜像广播的示例。
iOS
水平镜像:
let cameraSource = IVSMixedImageDeviceSourceConfiguration() cameraSource.size = CGSize(width: -320, height: 720) // Add 320 to position x since our width is -320 cameraSource.position = CGPoint(x: 320, y: 0)
垂直镜像:
let cameraSource = IVSMixedImageDeviceSourceConfiguration() cameraSource.size = CGSize(width: 320, height: -720) // Add 720 to position y since our height is -720 cameraSource.position = CGPoint(x: 0, y: 720)
Android
水平镜像:
val cameraConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(-320f, 180f)) // Add 320f to position x since our width is -320f setPosition(BroadcastConfiguration.Vec2(320f, 0f)) }
垂直镜像:
val cameraConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(320f, -180f)) // Add 180f to position y since our height is -180f setPosition(BroadcastConfiguration.Vec2(0f, 180f)) }
注意:此镜像与 ImagePreviewView
(Android)和 IVSImagePreviewView
(iOS)上的 setMirrored
方法不同。该方法仅影响设备上的本地预览视图,不会影响广播。