Casos de uso avançados para o SDK de Transmissão para iOS do IVS | Streaming de baixa latência - Amazon IVS

Casos de uso avançados para o SDK de Transmissão para iOS do IVS | Streaming de baixa latência

Aqui, apresentamos alguns casos de uso avançados. Comece com a configuração básica acima e continue aqui.

Criar uma configuração de transmissão do Android

Aqui, criamos uma configuração personalizada com dois slots de mixer que nos permitem vincular duas fontes de vídeo ao mixer. Um deles (custom) é tela cheia e fica posicionado atrás do outro (camera), que é menor e está localizado no canto inferior direito. Para o slot custom, não definimos uma posição, um tamanho ou um modo de aspecto. Como não definimos esses parâmetros, o slot usa as configurações de vídeo para tamanho e posição.

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 }() ]

Criar a sessão de transmissão (versão avançada)

Crie uma IVSBroadcastSession, como fez no exemplo básico, mas forneça sua configuração personalizada aqui. Forneça também nil para a matriz de dispositivos, pois vamos adicioná-los manualmente.

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

Iterar e anexar um dispositivo de câmera

Aqui nós fazemos a iteração por meio dos dispositivos de entrada que o SDK detectou. O SDK retornará apenas dispositivos incorporados no iOS. Mesmo que os dispositivos de áudio Bluetooth estejam conectados, eles serão exibidos como um dispositivo interno. Para obter mais informações, consulte Problemas conhecidos e soluções no SDK de Transmissão para iOS do IVS | Streaming de baixa latência.

Depois de encontrarmos um dispositivo que queremos usar, chamamos attachDevice para anexá-lo:

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 } }

Trocar câmeras

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

Criar uma fonte de entrada personalizada

Para inserir dados de som ou imagem gerados pela aplicação, use createImageSource ou createAudioSource. Os dois métodos criam dispositivos virtuais (IVSCustomImageSource e IVSCustomAudioSource) que podem ser vinculados ao mixer como qualquer outro dispositivo.

Os dispositivos retornados por ambos os métodos aceitam CMSampleBuffer por meio da função onSampleBuffer:

  • Para fontes de vídeo, o formato de pixel deve ser kCVPixelFormatType_32BGRA, 420YpCbCr8BiPlanarFullRange ou 420YpCbCr8BiPlanarVideoRange.

  • Para fontes de áudio, o buffer deve conter dados de PCM Linear.

Não é possível utilizar AVCaptureSession com entrada de câmera para alimentar uma fonte de imagem personalizada enquanto também usa um dispositivo de câmera fornecido pelo SDK de Transmissão. Se quiser usar várias câmeras simultaneamente, use AVCaptureMultiCamSession e forneça duas fontes de imagem personalizadas.

As fontes de imagem personalizadas devem ser usadas principalmente com conteúdo estático, como imagens, ou com conteúdo de vídeo:

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

Monitorar a conectividade de rede

É comum que os dispositivos móveis percam a conectividade de rede temporariamente e a recuperem enquanto estão em movimento. Por isso, é importante monitorar a conectividade de rede de sua aplicação e responder adequadamente quando as coisas mudam.

Quando a conexão com o transmissor for perdida, o estado do SDK de Transmissão será alterado para error e depois para disconnected. Você será notificado sobre essas alterações pelo IVSBroadcastSessionDelegate. Ao receber essas alterações de estado:

  1. Monitore o estado de conectividade de sua aplicação de transmissão e chame start com seu endpoint e chave de transmissão, assim que sua conexão for restaurada.

  2. Importante: monitore o retorno de chamada de estado delegada e verifique se o estado é alterado para connected depois de chamar start novamente.

Desconectar um dispositivo

Se você deseja desconectar e não substituir um dispositivo, desconecte-o com IVSDevice ou IVSDeviceDescriptor:

broadcastSession.detachDevice(currentCamera)

Integração com o ReplayKit

Para transmitir a tela e o áudio do sistema do dispositivo no iOS, é necessário integrá-lo com o ReplayKit. O SDK de Transmissão do Amazon IVS facilita a integração com o ReplayKit usando IVSReplayKitBroadcastSession. Na subclasse RPBroadcastSampleHandler, crie uma instância de IVSReplayKitBroadcastSession, então:

  • Iniciar a sessão em broadcastStarted

  • Interromper sessão em broadcastFinished

O objeto de sessão terá três fontes personalizadas para imagens da tela, áudio da aplicação e áudio do microfone. Passe o CMSampleBuffers fornecido em processSampleBuffer para essas fontes personalizadas.

Para lidar com a orientação do dispositivo, é necessário extrair metadados específicos do ReplayKit do buffer de amostra. Use o seguinte 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)) } }

É possível integrar o ReplayKit usando IVSBroadcastSession em vez de IVSReplayKitBroadcastSession. Porém, a variante específica do ReplayKit tem várias modificações para reduzir o espaço de memória interna, para permanecer dentro do teto de memória da Apple para extensões de transmissão.

Para avaliar a conexão do usuário antes de iniciar uma transmissão, use IVSBroadcastSession.recommendedVideoSettings para executar um breve teste. À medida que o teste for executado, você receberá várias recomendações, ordenadas da mais recomendada para a menos recomendada. Nessa versão do SDK, não é possível reconfigurar a IVSBroadcastSession atual, então você precisará desalocá-la e criar uma nova com as configurações recomendadas. Você continuará recebendo IVSBroadcastSessionTestResults até que result.status seja Success ou Error. É possível verificar o progresso com result.progress.

O Amazon IVS é compatível com uma taxa de bits de até 8,5 Mbps (para canais cujo type seja STANDARD ou ADVANCED), de modo que a maximumBitrate retornada por esse método nunca excede 8,5 Mbps. Para considerar pequenas flutuações da performance da rede, o initialBitrate recomendado retornado por esse método é ligeiramente menor do que a taxa de bits verdadeira medida no teste. (Geralmente, não é aconselhável usar 100% da largura de banda disponível.)

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 da reconexão automática

O IVS oferece suporte à reconexão automática a uma transmissão caso a transmissão pare inesperadamente sem chamar a API stop; por exemplo, uma perda temporária na conectividade de rede. Para ativar a reconexão automática, defina a propriedade enabled em IVSBroadcastConfiguration.autoReconnect como true.

Quando algo faz com que o fluxo pare inesperadamente, o SDK tenta novamente até 5 vezes, seguindo uma estratégia de recuo linear. Isso notifica sua aplicação sobre o estado da nova tentativa por meio da função IVSBroadcastSessionDelegate.didChangeRetryState.

Nos bastidores, a reconexão automática usa a funcionalidade de aquisição de fluxo do IVS, anexando um número de prioridade, começando com 1, ao final da chave de fluxo fornecida. Pela duração da instância IVSBroadcastSession, esse número é incrementado em 1 cada vez que uma reconexão é tentada. Isso significa que se a conexão do dispositivo for perdida 4 vezes durante uma transmissão e cada perda exigir de 1 a 4 tentativas, a prioridade da última transmissão poderá estar entre 5 e 17. Por isso, recomendamos que você não use a aquisição de fluxo do IVS a partir de outro dispositivo enquanto a reconexão automática estiver ativada no SDK para o mesmo canal. Não há garantias de qual prioridade o SDK estará usando no momento, e o SDK tentará se reconectar com uma prioridade mais alta se outro dispositivo assumir o controle.

Como usar vídeo de plano de fundo

Você pode dar continuidade a uma transmissão sem RelayKit, mesmo que a sua aplicação esteja em segundo plano.

Para economizar energia e manter as aplicações em primeiro plano ágeis, o iOS só oferece acesso à GPU a uma aplicação por vez. O SDK de Transmissão do Amazon IVS usa a GPU em diversos estágios do pipeline de vídeo, inclusive para compor várias fontes de entrada, escalar a imagem e codificá-la. Embora a aplicação de transmissão esteja em segundo plano, não há garantia de que o SDK possa executar qualquer uma dessas ações.

Para solucionar isso, use o método createAppBackgroundImageSource. Ele permite que o SDK continue transmitindo vídeo e áudio enquanto está em segundo plano. Ele retorna uma IVSBackgroundImageSource, que é uma IVSCustomImageSource normal com a inclusão de uma função finish. Cada CMSampleBuffer fornecido à fonte da imagem de plano de fundo é codificado na proporção fornecida pela sua IVSVideoConfiguration original. Os timestamps no CMSampleBuffer são ignorados.

Em seguida, o SDK escala e codifica essas imagens e as armazena em cache, e faz um loop automático desse feed quando a sua aplicação entra em segundo plano. Quando a sua aplicação retorna ao primeiro plano, os dispositivos de imagem anexados voltam a ficar ativos e o looping da transmissão pré-codificada é interrompido.

Para desfazer esse processo, use removeImageSourceOnAppBackgrounded. Não é necessário chamar esse método, a não ser que você queira reverter explicitamente o comportamento em segundo plano do SDK, caso contrário, ele será automaticamente limpo na desalocação da IVSBroadcastSession.

Observações: é altamente recomendável que você chame esse método como parte da configuração da sessão de transmissão antes que a sessão entre ao vivo. O método é caro (codifica vídeo) e, por isso, a performance de uma transmissão ao vivo enquanto esse método estiver em execução poderá ser degradada.

Exemplo: geração de uma imagem estática para vídeos de plano de fundo

Fornecer uma única imagem para a fonte de plano de fundo gera um GOP completo dessa imagem estática.

Veja um exemplo de uso de 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()

Como alternativa, em vez de criar um CIImage de uma cor sólida, você pode usar imagens em pacotes. O único código exibido aqui refere-se a como converter um UIImage em um CIImage para usar com a amostra 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)

Exemplo: vídeo com AVAssetImageGenerator

Você pode usar um AVAssetImageGenerator para gerar CMSampleBuffers de um AVAsset (e não de um AVAsset de uma transmissão de HLS):

// 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() } }

É possível gerar CVPixelBuffers usando um AVPlayer e uma AVPlayerItemVideoOutput. Contudo, isso exige o uso de um CADisplayLink e realiza uma execução quase em tempo real, enquanto o AVAssetImageGenerator pode processar quadros muito mais rápido.

Limitações

A sua aplicação precisa de direitos de áudio em segundo plano para evitar a suspensão ao entrar em segundo plano.

A createAppBackgroundImageSource só poderá ser chamada enquanto a sua aplicação estiver em primeiro plano, pois ela precisa de acesso à GPU para ser concluída.

A createAppBackgroundImageSource sempre codifica para um GOP completo. Por exemplo, se você tiver um intervalo de quadro-chave de dois segundos (o padrão) e estiver executando a 30 fps, ele codificará um múltiplo de 60 quadros.

  • Se menos de 60 quadros forem fornecidos, o último quadro será repetido até que 60 quadros sejam atingidos, independentemente do valor da opção de corte.

  • Se mais de 60 quadros forem fornecidos e a opção de corte for true, os últimos N quadros serão descartados, em que N é o restante do número total de quadros enviados dividido por 60.

  • Se mais de 60 quadros forem fornecidos e a opção de corte for false, o último quadro será repetido até que o próximo múltiplo de 60 quadros seja atingido.