

# Casos de uso avanzados del SDK de transmisión para iOS de IVS \| Transmisión de baja latencia
<a name="broadcast-ios-use-cases"></a>

Aquí presentamos algunos casos de uso avanzados. Comience con la configuración básica anterior y continúe aquí.

## Creación de una configuración de transmisión
<a name="broadcast-ios-create-configuration"></a>

Aquí creamos una configuración personalizada con dos espacios en el mezclador que nos permiten vincular dos fuentes de video al mezclador. Uno (`custom`) está a pantalla completa y se presenta detrás del otro (`camera`), que es más pequeño y en la esquina inferior derecha. Tenga en cuenta que para el espacio `custom` no establecemos posición, tamaño ni modo de aspecto. Debido a que no establecemos estos parámetros, el espacio utiliza la configuración de video para el tamaño y la posición.

```
let config = IVSBroadcastConfiguration()
try config.audio.setBitrate(128_000)
try config.video.setMaxBitrate(3_500_000)
try config.video.setMinBitrate(500_000)
try config.video.setInitialBitrate(1_500_000)
try config.video.setSize(CGSize(width: 1280, height: 720))
config.video.defaultAspectMode = .fit
config.mixer.slots = [
    try {
        let slot = IVSMixerSlotConfiguration()
        // Do not automatically bind to a source
        slot.preferredAudioInput = .unknown
        // Bind to user image if unbound
        slot.preferredVideoInput = .userImage
        try slot.setName("custom")
        return slot
    }(),
    try {
        let slot = IVSMixerSlotConfiguration()
        slot.zIndex = 1
        slot.aspect = .fill
        slot.size = CGSize(width: 300, height: 300)
        slot.position = CGPoint(x: config.video.size.width - 400, y: config.video.size.height - 400)
        try slot.setName("camera")
        return slot
    }()
]
```

## Creación de la sesión de transmisión (versión avanzada)
<a name="broadcast-ios-create-session-advanced"></a>

Cree una `IVSBroadcastSession` como lo hizo en el [ejemplo básico](broadcast-ios-getting-started.md#broadcast-ios-create-session), pero proporcione su configuración personalizada aquí. Proporcione también `nil` para la matriz de dispositivos, ya que los agregaremos manualmente.

```
let broadcastSession = try IVSBroadcastSession(
   configuration: config, // The configuration we created above
   descriptors: nil, // We’ll manually attach devices after
   delegate: self)
```

## Iterar y adjuntar un dispositivo de cámara
<a name="broadcast-ios-attach-camera"></a>

Aquí iteramos a través de dispositivos de entrada que el SDK ha detectado. El SDK solo devolverá dispositivos integrados en iOS. Incluso si los dispositivos de audio Bluetooth están conectados, aparecerán como un dispositivo integrado. Para obtener más información, consulte [Problemas conocidos y soluciones alternativas del SDK de transmisión para iOS de IVS \| Transmisión de baja latencia](broadcast-ios-issues.md).

Una vez que encontramos un dispositivo que queremos usar, llamamos a `attachDevice` para adjuntarlo:

```
let frontCamera = IVSBroadcastSession.listAvailableDevices()
    .filter { $0.type == .camera && $0.position == .front }
    .first
if let camera = frontCamera {
    broadcastSession.attach(camera, toSlotWithName: "camera") { device, error in
        // check error
    }
}
```

## Cámaras de intercambio
<a name="broadcast-ios-swap-cameras"></a>

```
// This assumes you’ve kept a reference called `currentCamera` that points to the current camera.
let wants: IVSDevicePosition = (currentCamera.descriptor().position == .front) ? .back : .front
// Remove the current preview view since the device will be changing.
previewView.subviews.forEach { $0.removeFromSuperview() }
let foundCamera = IVSBroadcastSession
        .listAvailableDevices()
        .first { $0.type == .camera && $0.position == wants }
guard let newCamera = foundCamera else { return }
broadcastSession.exchangeOldDevice(currentCamera, withNewDevice: newCamera) { newDevice, _ in
    currentCamera = newDevice
    if let camera = newDevice as? IVSImageDevice {
        do {
            previewView.addSubview(try finalCamera.previewView())
        } catch {
            print("Error creating preview view \(error)")
        }
    }
}
```

## Crear una fuente de entrada personalizada
<a name="broadcast-ios-create-input-source"></a>

Para introducir los datos de sonido o imagen que genera la aplicación, utilice `createImageSource` o `createAudioSource`. Ambos métodos crean dispositivos virtuales (`IVSCustomImageSource` y `IVSCustomAudioSource`) que se pueden vincular al mezclador como cualquier otro dispositivo.

Los dispositivos devueltos por ambos métodos aceptan un `CMSampleBuffer` a través de su función `onSampleBuffer`:
+ Para las fuentes de video, el formato de píxel debe ser `kCVPixelFormatType_32BGRA`, `420YpCbCr8BiPlanarFullRange` o bien `420YpCbCr8BiPlanarVideoRange`.
+ En el caso de las fuentes de audio, el búfer debe contener datos PCM lineales.

No puede utilizar una `AVCaptureSession` con entrada de cámara para alimentar una fuente de imagen personalizada mientras que también utiliza un dispositivo de cámara proporcionado por el SDK de transmisión. Si desea utilizar varias cámaras simultáneamente, utilice `AVCaptureMultiCamSession` y proporcione dos fuentes de imágenes personalizadas.

Las fuentes de imágenes personalizadas se deben utilizar principalmente con contenido estático, como imágenes, o con contenido de video:

```
let customImageSource = broadcastSession.createImageSource(withName: "video")
try broadcastSession.attach(customImageSource, toSlotWithName: "custom")
```

## Monitorear la conectividad de red
<a name="broadcast-ios-network-connection"></a>

Es común que los dispositivos móviles se desconecten temporalmente y recuperen la conectividad de red durante una transmisión. Debido a esto, es importante monitorear la conectividad de red de la aplicación y responder adecuadamente cuando las cosas cambien. 

Cuando se pierde la conexión de la emisora, el estado del SDK de transmisión cambiará a `error` y luego a `disconnected`. Se le notificará de estos cambios a través del `IVSBroadcastSessionDelegate`. Cuando reciba estos cambios de estado:

1. Monitoree el estado de conectividad de su aplicación de transmisión y llame a `start` con el punto de enlace y la clave de transmisión, una vez que se haya restaurado la conexión.

1. **Importante:** Supervise la devolución de llamada del delegado de estado y asegúrese de que el estado cambie a `connected` después de llamar a `start` de nuevo.

## Desconectar un dispositivo
<a name="broadcast-ios-detach-device"></a>

Si desea desconectar y no reemplazar un dispositivo, desconéctelo con `IVSDevice` o `IVSDeviceDescriptor`:

```
broadcastSession.detachDevice(currentCamera)
```

## Integración de ReplayKit
<a name="broadcast-ios-replaykit"></a>

Para transmitir la pantalla del dispositivo y el audio del sistema en iOS, debe integrarse con [ReplayKit](https://developer.apple.com/documentation/replaykit?language=objc). El SDK de transmisión de Amazon IVS facilita la integración con ReplayKit mediante la `IVSReplayKitBroadcastSession`. En la subclase `RPBroadcastSampleHandler`, cree una instancia de `IVSReplayKitBroadcastSession`, a continuación:
+ Inicie la sesión en `broadcastStarted`
+ Detenga la sesión en `broadcastFinished`

El objeto de sesión tendrá tres fuentes personalizadas para imágenes de pantalla, audio de aplicación y audio de micrófono. Pase el `CMSampleBuffers` proporcionado en `processSampleBuffer` a esas fuentes personalizadas.

Para controlar la orientación del dispositivo, debe extraer metadatos específicos de ReplayKit del búfer de muestra. Utilice el siguiente código:

```
let imageSource = session.systemImageSource;
if let orientationAttachment = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber,
    let orientation = CGImagePropertyOrientation(rawValue: orientationAttachment.uint32Value) {
    switch orientation {
    case .up, .upMirrored:
        imageSource.setHandsetRotation(0)
    case .down, .downMirrored:
        imageSource.setHandsetRotation(Float.pi)
    case .right, .rightMirrored:
        imageSource.setHandsetRotation(-(Float.pi / 2))
    case .left, .leftMirrored:
        imageSource.setHandsetRotation((Float.pi / 2))
    }
}
```

Es posible integrar ReplayKit mediante `IVSBroadcastSession` en lugar de `IVSReplayKitBroadcastSession`. Sin embargo, la variante específica de ReplayKit tiene varias modificaciones para reducir la huella de memoria interna, a fin de mantenerse dentro del límite de memoria de Apple para extensiones de transmisión.

## Obtención de la configuración de transmisión recomendada
<a name="broadcast-ios-recommended-settings"></a>

Para evaluar la conexión de su usuario antes de iniciar una transmisión, utilice las `IVSBroadcastSession.recommendedVideoSettings` para ejecutar una breve prueba. A medida que se ejecuta la prueba, recibirá varias recomendaciones, ordenadas de la más recomendada a la menos recomendada. En esta versión del SDK, no es posible volver a configurar la `IVSBroadcastSession` actual, por lo que tendrá que eliminar la asignación y luego crear una nueva sesión con la configuración recomendada. Seguirá recibiendo `IVSBroadcastSessionTestResults` hasta que el `result.status` sea `Success` o `Error`. Puede verificar el progreso con `result.progress`.

Amazon IVS admite una velocidad de bits máxima de 8,5 Mbps (para canales cuyo `type` es `STANDARD` o `ADVANCED`), por lo que la `maximumBitrate` devuelta por este método nunca supera los 8,5 Mbps. Para tener en cuenta las pequeñas fluctuaciones en el rendimiento de la red, la `initialBitrate` recomendada que devuelve este método es ligeramente menor que la velocidad de bits verdadera medida en la prueba. (Por lo general, no es aconsejable utilizar el 100 % de la banda ancha disponible).

```
func runBroadcastTest() {
    self.test = session.recommendedVideoSettings(with: IVS_RTMPS_URL, streamKey: IVS_STREAMKEY) { [weak self] result in
        if result.status == .success {
            self?.recommendation = result.recommendations[0];
        }
    }
}
```

## Uso de la reconexión automática
<a name="broadcast-ios-auto-reconnect"></a>

IVS es compatible con la reconexión automática a una transmisión si se detiene inesperadamente sin llamar a la API `stop`; por ejemplo, si se produce una pérdida temporal de la conectividad de la red. Para habilitar la reconexión automática, establezca la propiedad `enabled` de `IVSBroadcastConfiguration.autoReconnect` en `true`.

Cuando la transmisión se detiene inesperadamente, el SDK intentar la reconexión hasta 5 veces, con una estrategia de espera lineal. Notifica a su aplicación sobre el estado de reintento con la función `IVSBroadcastSessionDelegate.didChangeRetryState`.

En segundo plano, la reconexión automática utiliza la funcionalidad [stream-takeover](streaming-config.md#streaming-config-stream-takeover) de IVS al agregar un número de prioridad, comenzando por 1, al final de la clave de transmisión proporcionada. Mientras dure la instancia `IVSBroadcastSession`, ese número se incrementa en 1 cada vez que se intenta una reconexión. Esto significa que si la conexión del dispositivo se pierde 4 veces durante una transmisión y cada pérdida requiere entre 1 y 4 intentos de reconexión, la prioridad de la última transmisión activa podría ser de entre 5 y 17. Es por esto que *recomendamos que no use la toma de control de la transmisión de IVS desde otro dispositivo mientras esté habilitada la reconexión automática en el SDK del mismo canal*. No hay garantía de qué prioridad del SDK se esté utilizando en ese momento y el SDK intentará reconectarse con una prioridad más alta si otro dispositivo toma el control.

## Usar video de fondo
<a name="broadcast-ios-background-video"></a>

Puede continuar una transmisión que no sea de RelayKit, incluso con la aplicación en segundo plano.

Para ahorrar energía y mantener la capacidad de respuesta de las aplicaciones en primer plano, iOS solo permite acceder a la GPU a una aplicación a la vez. El SDK de transmisión de Amazon IVS utiliza la GPU en varias etapas de la canalización de video, incluida la composición de varias fuentes de entrada, el escalado de la imagen y la codificación de la imagen. Si bien la aplicación de transmisión se encuentra en segundo plano, no hay garantía de que el SDK pueda realizar alguna de estas acciones.

Para resolverlo, utilice el método `createAppBackgroundImageSource`. Permite que el SDK continúe transmitiendo video y audio mientras está en segundo plano. Devuelve un `IVSBackgroundImageSource`, que es un `IVSCustomImageSource` normal con una función `finish` adicional. Cada `CMSampleBuffer` proporcionado a la fuente de imagen de fondo está codificado a la tasa de fotogramas proporcionada por su `IVSVideoConfiguration` original. Se ignoran las marcas de tiempo de `CMSampleBuffer`.

A continuación, el SDK escala y codifica esas imágenes y las almacena en caché, haciendo un bucle automáticamente de esa fuente cuando la aplicación entra en segundo plano. Cuando la aplicación vuelve al primer plano, los dispositivos de imagen asociados vuelven a activarse y la transmisión precodificada deja de hacer el bucle.

Para deshacer este proceso, utilice `removeImageSourceOnAppBackgrounded`. No es necesario usarlo a menos que desee revertir explícitamente el comportamiento en segundo plano del SDK. De lo contrario, se limpiará automáticamente al desasignar `IVSBroadcastSession`.

**Notas:** *Le recomendamos encarecidamente que llame a este método como parte de la configuración de la sesión de transmisión, antes de que la sesión se publique.* El método es caro (codifica video), por lo que el rendimiento de una transmisión en directo mientras se ejecuta este método puede verse degradado.

### Ejemplo: generación de una imagen estática para video de fondo
<a name="background-video-example-static-image"></a>

Proporcionar una sola imagen a la fuente de fondo genera un grupo de imágenes completo de esa imagen estática.

A continuación, se muestra un ejemplo con CIImage:

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Create a CIImage of the color red.
let ciImage = CIImage(color: .red)

// Convert the CIImage to a CVPixelBuffer
let attrs = [
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
] as CFDictionary

var pixelBuffer: CVPixelBuffer!
CVPixelBufferCreate(kCFAllocatorDefault,
                    videoConfig.width,
                    videoConfig.height,
                    kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                    attrs,
                    &pixelBuffer)

let context = CIContext()
context.render(ciImage, to: pixelBuffer)

// Submit to CVPixelBuffer and finish the source
source.add(pixelBuffer)
source.finish()
```

Alternativamente, en lugar de crear una imagen CIImage de un color sólido, puede utilizar imágenes empaquetadas. El único código que se muestra aquí es cómo convertir un UIImage en un CIImage para utilizarlo con el ejemplo anterior:

```
// Load the pre-bundled image and get it’s CGImage
guard let cgImage = UIImage(named: "image")?.cgImage else {
    return
}

// Create a CIImage from the CGImage
let ciImage = CIImage(cgImage: cgImage)
```

### Ejemplo: video con AVassetImageGenerator
<a name="background-video-example-avassetimagegenerator"></a>

Puede utilizar un `AVAssetImageGenerator` para generar `CMSampleBuffers` de un `AVAsset` (aunque no es una transmisión de HLS `AVAsset`):

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Find the URL for the pre-bundled MP4 file
guard let url = Bundle.main.url(forResource: "sample-clip", withExtension: "mp4") else {
    return
}
// Create an image generator from an asset created from the URL.
let generator = AVAssetImageGenerator(asset: AVAsset(url: url))
// It is important to specify a very small time tolerance.
generator.requestedTimeToleranceAfter = .zero
generator.requestedTimeToleranceBefore = .zero

// At 30 fps, this will generate 4 seconds worth of samples.
let times: [NSValue] = (0...120).map { NSValue(time: CMTime(value: $0, timescale: CMTimeScale(config.video.targetFramerate))) }
var completed = 0

let context = CIContext(options: [.workingColorSpace: NSNull()])

// Create a pixel buffer pool to efficiently feed the source
let attrs = [
    kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferWidthKey: videoConfig.width,
    kCVPixelBufferHeightKey: videoConfig.height,
] as CFDictionary
var pool: CVPixelBufferPool!
CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs, &pool)

generator.generateCGImagesAsynchronously(forTimes: times) { requestTime, image, actualTime, result, error in
    if let image = image {
        // convert to CIImage then CVpixelBuffer
        let ciImage = CIImage(cgImage: image)
        var pixelBuffer: CVPixelBuffer!
        CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
        context.render(ciImage, to: pixelBuffer)
        source.add(pixelBuffer)
    }
    completed += 1
    if completed == times.count {
        // Mark the source finished when all images have been processed
        source.finish()
    }
}
```

Se puede generar `CVPixelBuffers` usando un `AVPlayer` y `AVPlayerItemVideoOutput`. Sin embargo, eso requiere utilizar un `CADisplayLink`. Además, se ejecuta casi en tiempo real, mientras que `AVAssetImageGenerator` puede procesar los fotogramas mucho más rápido.

### Limitaciones
<a name="background-video-limitations"></a>

Su aplicación necesita la [autorización de audio de fondo](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes) para evitar que se suspenda después de entrar en segundo plano.

`createAppBackgroundImageSource`Solo se puede llamar a mientras la aplicación se encuentra en primer plano, puesto que necesita acceder a la GPU para finalizar.

`createAppBackgroundImageSource` siempre codifica un grupo de imágenes completo. Por ejemplo, si tiene un intervalo de fotogramas clave de 2 segundos (el predeterminado) y se ejecuta a 30 fps, codifica un múltiplo de 60 fotogramas.
+ Si se proporcionan menos de 60 fotogramas, el último fotograma se repite hasta que se alcanzan los 60 fotogramas, independientemente del valor en la opción de recorte.
+ Si se proporcionan más de 60 fotogramas y la opción de recorte es `true`, se eliminan los últimos N fotogramas, siendo N el resto del número total de fotogramas enviados dividido por 60.
+ Si se proporcionan más de 60 fotogramas y la opción de recorte es `false`, el último fotograma se repite hasta que se alcanza el siguiente múltiplo de 60 fotogramas.