

# IVS 广播 SDK：混合设备
<a name="broadcast-mixed-devices"></a>

混合设备是音频和视频设备，采用多个输入源并生成单个输出。混合设备是一项强大的功能，可让您定义和管理多个屏幕上的（视频）元素和音轨。您可以组合来自多个来源的视频和音频，例如摄像头、麦克风、屏幕截图以及应用程序生成的音频和视频。您可以使用转换围绕流式传输到 IVS 的视频移动这些源，然后在流中添加和移除源。

混合设备分为图像和音频两种。要创建一个混合图像设备，请调用：

Android 上的 `DeviceDiscovery.createMixedImageDevice()`

iOS 上的 `IVSDeviceDiscovery.createMixedImageDevice()`

返回的设备可以附加到 `BroadcastSession`（低延迟流式传输）或 `Stage`（实时流式传输），就像任何其他设备一样。

## 术语
<a name="broadcast-mixed-devices-terminology"></a>

![\[IVS 广播混合设备术语。\]](http://docs.aws.amazon.com/zh_cn/ivs/latest/LowLatencyUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| 租期 | 说明 | 
| --- | --- | 
| 设备 | 一种硬件或软件组件，用于生成到音频或图像输入。例如，设备包括麦克风、摄像头、蓝牙耳机和屏幕截图或自定义图像输入等虚拟设备。 | 
| 混合设备 | 可以像其他任何 `Device` 一样附加到 `BroadcastSession` 的 `Device`，但带有允许添加 `Source` 对象的附加 API。混合设备具有内部混合器，其合成音频或图像，从而产生单个输出音频和图像流。 混合设备分为音频或图像两种。  | 
| 混合设备配置 | 混合设备的配置对象。对于混合图像设备，这将配置诸如尺寸和帧率之类的属性。对于混合音频设备，这将配置通道计数。 | 
|  来源 | 定义视觉元素在屏幕上的位置以及音频混合中音轨属性的容器。可以使用零个或多个源配置混合设备。向源提供了影响源媒体使用方式的配置。上图显示了四个图像源： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/ivs/latest/LowLatencyUserGuide/broadcast-mixed-devices.html)  | 
| 源配置 |  进入混合设备的源的配置对象。完整的配置对象如下所述。  | 
| Transition | 要将插槽移动到新位置或更改其部分属性，请使用 `MixedDevice.transitionToConfiguration()`。此方法需要： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/ivs/latest/LowLatencyUserGuide/broadcast-mixed-devices.html) | 

## 混合音频设备
<a name="broadcast-mixed-audio-device"></a>

### 配置
<a name="broadcast-mixed-audio-device-configuration"></a>

Android 上的 `MixedAudioDeviceConfiguration`

iOS 上的 `IVSMixedAudioDeviceConfiguration`


| 名称 | Type | 说明 | 
| --- | --- | --- | 
| `channels` | 整数 | 音频混合器的输出通道数。有效值：1、2。1 为单声道音频；2 为立体声音频。默认值：2。 | 

### 源配置
<a name="broadcast-mixed-audio-device-source-configuration"></a>

Android 上的 `MixedAudioDeviceSourceConfiguration`

iOS 上的 `IVSMixedAudioDeviceSourceConfiguration`


| 名称 | Type | 说明 | 
| --- | --- | --- | 
| `gain` | 浮点型 | 音频增益。这是一个乘数，因此任何高于 1 的值都会提高增益；任何低于 1 的值都会减小增益。有效值：0-2。默认值：1。 | 

## 混合图像设备
<a name="broadcast-mixed-image-device"></a>

### 配置
<a name="broadcast-mixed-image-device-configuration"></a>

Android 上的 `MixedImageDeviceConfiguration`

iOS 上的 `IVSMixedImageDeviceConfiguration`


| 名称 | Type | 说明 | 
| --- | --- | --- | 
| `size` | Vec2 | 视频画布的大小。 | 
| `targetFramerate` | 整数 | 混合设备的每秒目标帧数。通常情况下，该值应能够得到满足，但是在某些情况下（例如 CPU 或 GPU 负载过高），系统可能会丢弃帧。 | 
| `transparencyEnabled` | 布尔值 | 这允许使用图像源配置上的 `alpha` 属性进行混合。将其设置为 `true` 会增加内存和 CPU 消耗。默认值：`false`。 | 

### 源配置
<a name="broadcast-mixed-image-device-source-configuration"></a>

Android 上的 `MixedImageDeviceSourceConfiguration`

iOS 上的 `IVSMixedImageDeviceSourceConfiguration`


| 名称 | Type | 说明 | 
| --- | --- | --- | 
| `alpha` | 浮点型 | 插槽的 Alpha。这是与图像中的任何 Alpha 值相乘的结果。有效值：0-1。0 表示完全透明，1 表示完全不透明。默认值：1。 | 
| `aspect` | AspectMode | 适用于插槽中渲染的任何图像的宽高比模式。有效值： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/ivs/latest/LowLatencyUserGuide/broadcast-mixed-devices.html) 默认值：`Fit`  | 
| `fillColor` | Vec4 | 当插槽和图像的宽高比不匹配时，填充用于 `aspect Fit` 的颜色。格式为（red、green、blue、alpha）。有效值（对于每个通道）：0-1。默认值：(0, 0, 0, 0)。 | 
| `position` | Vec2 | 插槽相对于画布左上角的位置（以像素为单位）。插槽的原点也在左上角。 | 
| `size` | Vec2 | 插槽的大小（以像素为单位）。设置此值也会将 `matchCanvasSize` 设置为 `false`。默认值：(0, 0)；但是，因为 `matchCanvasSize` 默认为 `true`，插槽的渲染大小就是画布大小，而不是 (0, 0)。 | 
| `zIndex` | 浮点型 | 插槽的相对顺序。`zIndex` 值较高的插槽绘制在 `zIndex` 值较低的插槽之上。 | 

## 创建和配置混合图像设备
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[配置广播会话以进行混合。\]](http://docs.aws.amazon.com/zh_cn/ivs/latest/LowLatencyUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


在这里，我们创建了一个类似于本指南开头的场景，其中包含三个屏幕元素：
+ 左下角的摄像头插槽。
+ 右下角的徽标叠加插槽。
+ 右上角用于电影的插槽。

请注意，画布的原点是左上角，插槽也是一样。因此，将插槽定位在 (0, 0) 会将其放在左上角，且整个插槽可见。

### iOS
<a name="broadcast-mixed-image-device-creating-configuring-ios"></a>

```
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
<a name="broadcast-mixed-image-device-creating-configuring-android"></a>

```
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)
```

## 移除源
<a name="broadcast-mixed-devices-removing-sources"></a>

要移除源，请使用要移除的 `Source` 对象调用 `MixedDevice.remove`。

## 动画转换
<a name="broadcast-mixed-devices-animations-transitions"></a>

转换方法用新配置替换源的配置。通过将持续时间设置为大于 0（以秒为单位），可以将此替换制作为随时间推移的动画。

### 哪些属性可以制作成动画？
<a name="broadcast-mixed-devices-animations-properties"></a>

插槽结构中并非所有属性都可以制作成动画。基于 Float 类型的任何属性都可以制作成动画；其他属性在动画的开始或结束时生效。


| 名称 | 可以制作成动画吗？ | 影响点 | 
| --- | --- | --- | 
| `Audio.gain` | 是 | 已插入 | 
| `Image.alpha` | 是 | 已插入 | 
| `Image.aspect` | 否 | 早于 | 
| `Image.fillColor` | 是 | 已插入 | 
| `Image.position` | 是 | 已插入 | 
| `Image.size` | 是 | 已插入 | 
| `Image.zIndex` 注意：`zIndex` 在 3D 空间中移动 2D 平面，因此当两个平面在动画中间的某个点交叉时，就会发生转换。这可以计算出来，但具体取决于开始和结束 `zIndex` 值。为了实现更顺畅的转换，请将其与 `alpha` 结合使用。  | 是 | 未知 | 

### 简单示例
<a name="broadcast-mixed-devices-animations-examples"></a>

以下是使用上述[创建和配置混合图像设备](#broadcast-mixed-image-device-creating-configuring)中定义的配置进行全屏摄像头接管的示例。将其在 0.5 秒以内的变化制作成动画。

#### iOS
<a name="broadcast-mixed-devices-animations-examples-ios"></a>

```
// 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
<a name="broadcast-mixed-devices-animations-examples-android"></a>

```
// 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")
    }
}
```

## 镜像广播
<a name="broadcast-mixed-devices-mirroring"></a>


| 要在此方向镜像广播中附加的映像设备... | 使用负值表示... | 
| --- | --- | 
| 水平 | 插槽的宽度 | 
| 垂直 | 插槽的高度 | 
| 水平和垂直方向 | 槽的宽度和高度 | 

需要按相同的值调整位置，以便在镜像时将槽置于正确的位置。

以下是水平和垂直镜像广播的示例。

### iOS
<a name="broadcast-mixed-devices-mirroring-ios"></a>

水平镜像：

```
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
<a name="broadcast-mixed-devices-mirroring-android"></a>

水平镜像：

```
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` 方法不同。该方法仅影响设备上的本地预览视图，不会影响广播。