IVS Broadcast SDK: Mixed Devices
Mixed devices are audio and video devices that take multiple input sources and generate a single output. Mixing devices is a powerful feature that lets you define and manage multiple on-screen (video) elements and audio tracks. You can combine video and audio from multiple sources such as cameras, microphones, screen captures, and audio and video generated by your app. You can use transitions to move these sources around the video that you stream to IVS, and add to and remove sources mid-stream.
Mixed devices come in image and audio flavors. To create a mixed image device, call:
DeviceDiscovery.createMixedImageDevice() on Android
IVSDeviceDiscovery.createMixedImageDevice() on iOS
The returned device can be attached to a BroadcastSession (low-latency
streaming) or Stage (real-time streaming), like any other device.
Terminology
| Term | Description |
|---|---|
Device |
A hardware or software component that produces audio or image input. Examples of devices are microphones, cameras, Bluetooth headsets, and virtual devices such as screen captures or custom-image inputs. |
Mixed Device |
A Mixed devices come in either audio or image flavors. |
Mixed device configuration |
A configuration object for the mixed device. For mixed image devices, this configures properties like dimensions and framerate. For mixed audio devices, this configures the channel count. |
|
Source |
A container that defines a visual element’s position on screen and an audio track’s properties in the audio mix. A mixed device can be configured with zero or more sources. Sources are given a configuration that affects how the source’s media are used. The image above shows four image sources:
|
Source Configuration |
A configuration object for the source going into a mixed device. The full configuration objects are described below.. |
Transition |
To move a slot to a new position or change some of its
properties, use
|
Mixed Audio Device
Configuration
MixedAudioDeviceConfiguration on Android
IVSMixedAudioDeviceConfiguration on iOS
| Name | Type | Description |
|---|---|---|
|
Integer |
Number of output channels from the audio mixer. Valid values: 1, 2. 1 is mono audio; 2, stereo audio. Default: 2. |
Source Configuration
MixedAudioDeviceSourceConfiguration on Android
IVSMixedAudioDeviceSourceConfiguration on iOS
| Name | Type | Description |
|---|---|---|
|
Float |
Audio gain. This is a multiplier, so any value above 1 increases the gain; any value below 1, decreases it. Valid values: 0-2. Default: 1. |
Mixed Image Device
Configuration
MixedImageDeviceConfiguration on Android
IVSMixedImageDeviceConfiguration on iOS
| Name | Type | Description |
|---|---|---|
|
Vec2 |
Size of the video canvas. |
|
Integer |
Number of target frames per second for the mixed device. On average, this value should be met, but the system may drop frames under certain circumstances (e.g., high CPU or GPU load). |
|
Boolean |
This enables blending using the |
Source Configuration
MixedImageDeviceSourceConfiguration on Android
IVSMixedImageDeviceSourceConfiguration on iOS
| Name | Type | Description |
|---|---|---|
|
Float |
Alpha of the slot. This is multiplicative with any alpha values in the image. Valid values: 0-1. 0 is fully transparent and 1 is fully opaque. Default: 1. |
|
AspectMode | Aspect-ratio mode for any image rendered in the slot. Valid values:
Default: |
|
Vec4 |
Fill color to be used with |
|
Vec2 |
Slot position (in pixels), relative to the top-left corner of the canvas. The origin of the slot also is top-left. |
|
Vec2 |
Size of the slot, in pixels. Setting this value also sets
|
|
Float |
Relative ordering of slots. Slots with higher
|
Creating and Configuring a Mixed Image Device
Here, we create a scene similar to the one at the beginning of this guide, with three on-screen elements:
-
Bottom-left slot for a camera.
-
Bottom-right slot for a logo overlay.
-
Top-right slot for a movie.
Note that the origin for the canvas is the top-left corner and this is the same for the slots. Hence, positioning a slot at (0, 0) puts it in the top-left corner with the entire slot visible.
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)
Removing Sources
To remove a source, call MixedDevice.remove with the Source
object you want to remove.
Animations with Transitions
The transition method replaces a source’s configuration with a new configuration. This replacement can be animated over time by setting a duration higher than 0, in seconds.
Which Properties Can Be Animated?
Not all properties in the slot structure can be animated. Any properties based on Float types can be animated; other properties take effect at either the start or end of the animation.
| Name | Can It Be Animated? | Impact Point |
|---|---|---|
|
Yes |
Interpolated |
|
Yes |
Interpolated |
|
No |
End |
|
Yes |
Interpolated |
|
Yes |
Interpolated |
|
Yes |
Interpolated |
Note: The |
Yes |
Unknown |
Simple Examples
Below are examples of a full-screen camera takeover using the configuration defined above in Creating and Configuring a Mixed Image Device. This is animated over 0.5 seconds.
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") } }
Mirroring the Broadcast
| To mirror an attached image device in the broadcast in this direction … | Use a negative value for … |
|---|---|
Horizontally |
The width of the slot |
Vertically |
The height of the slot |
Both horizontally and vertically |
Both the width and height of the slot |
The position will need to be adjusted by the same value, to put the slot in the correct position when mirrored.
Below are examples for mirroring the broadcast horizontally and vertically.
iOS
Horizontal mirroring:
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)
Vertical mirroring:
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
Horizontal mirroring:
val cameraConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(-320f, 180f)) // Add 320f to position x since our width is -320f setPosition(BroadcastConfiguration.Vec2(320f, 0f)) }
Vertical mirroring:
val cameraConfig = MixedImageDeviceSourceConfiguration().apply { setSize(BroadcastConfiguration.Vec2(320f, -180f)) // Add 180f to position y since our height is -180f setPosition(BroadcastConfiguration.Vec2(0f, 180f)) }
Note: This mirroring is different than the setMirrored method on
ImagePreviewView (Android) and IVSImagePreviewView
(iOS). That method affects only the local preview view on the device and does not
impact the broadcast.