

# SDK de transmisión de IVS: filtros de cámara externos \$1 Transmisión en tiempo real
<a name="broadcast-3p-camera-filters"></a>

Esta guía supone que ya está familiarizado con las fuentes de [imágenes personalizadas](broadcast-custom-image-sources.md) y que ha integrado el [SDK de transmisión en tiempo real de IVS](broadcast.md) en su aplicación.

Los filtros de cámara permiten a los creadores de transmisiones en directo aumentar o modificar su aspecto facial o de fondo. Esto puede aumentar la participación de los espectadores, atraerlos y mejorar la experiencia de transmisión en directo.

# Integración de filtros de cámara externos
<a name="broadcast-3p-camera-filters-integrating"></a>

Puede integrar los SDK de filtros de cámara externos con el SDK de transmisión de IVS introduciendo la salida del SDK de filtros en una [fuente de entrada de imágenes personalizada](broadcast-custom-image-sources.md). Una fuente de entrada de imágenes personalizada permite que una aplicación proporcione su propia entrada de imagen al SDK de transmisión. El SDK de un proveedor de filtros externos puede gestionar el ciclo de vida de la cámara para procesar las imágenes de la cámara, aplicar un efecto de filtro y emitirlas en un formato que se pueda pasar a una fuente de imágenes personalizada.

![\[Integración de los SDK de filtros de cámara externos con el SDK de transmisión de IVS introduciendo la salida del SDK de filtros en una fuente de entrada de imágenes personalizada.\]](http://docs.aws.amazon.com/es_es/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Consulte la documentación de su proveedor de filtros externos para conocer los métodos integrados para convertir un fotograma de cámara, con el efecto de filtro, aplicado a un formato que se pueda pasar a una [fuente de entrada de imágenes personalizada](broadcast-custom-image-sources.md). El proceso varía según la versión del SDK de transmisión de IVS que se utilice:
+ **Web**: el proveedor de filtros debe poder renderizar su salida en un elemento de lienzo. Se puede usar el método [CaptureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) para devolver un MediaStream del contenido del lienzo. El MediaStream se puede convertir en una instancia de [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) y publicarse en un escenario.
+ **Android**: el SDK del proveedor de filtros puede renderizar un marco en un dispositivo Android `Surface` proporcionado por el SDK de transmisión de IVS o convertir el marco en un mapa de bits. Si utiliza un mapa de bits, puede renderizarlo en el `Surface` subyacente proporcionado por la fuente de imagen personalizada, desbloqueándolo y escribiéndolo en un lienzo.
+ **iOS**: el SDK de un proveedor de filtros externo debe proporcionar un marco de cámara con un efecto de filtro aplicado como `CMSampleBuffer`. Consulte la documentación del SDK de su proveedor de filtros externo para obtener información sobre cómo obtener un `CMSampleBuffer` como resultado final después de procesar una imagen de cámara.

# Uso de BytePlus con el SDK de transmisión de IVS
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

Este documento explica cómo utilizar el SDK de BytePlus Effects con el SDK de transmisión de IVS.

## Android
<a name="integrating-byteplus-android"></a>

### Instalación y configuración del SDK de BytePlus Effects
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Consulte la [Guía de acceso de Android](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) para BytePlus para obtener detalles sobre cómo instalar, inicializar y configurar el SDK de BytePlus Effects.

### Configuración de la fuente de imagen personalizada
<a name="integrating-byteplus-android-setup-image-source"></a>

Tras inicializar el SDK, alimente los fotogramas de cámara procesados con un efecto de filtro aplicado a una fuente de entrada de imágenes personalizada. Para ello, cree una instancia de un objeto `DeviceDiscovery` y cree una fuente de imagen personalizada. Observe que cuando utiliza una fuente de entrada de imagen personalizada para el control personalizado de la cámara, el SDK de transmisión ya no es responsable de administrar la cámara. En cambio, la aplicación es responsable de manejar correctamente el ciclo de vida de la cámara.

#### Java
<a name="integrating-byteplus-android-setup-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)
```

### Convierta la salida en un mapa de bits y la transmisión en una fuente de entrada de imágenes personalizada
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Para permitir que los fotogramas de cámara con un efecto de filtro aplicado desde el SDK de BytePlusEffect se reenvíen directamente al SDK de transmisión de IVS, convierta la salida de una textura del SDK de BytePlus Effects en un mapa de bits. Cuando se procesa una imagen, el SDK invoca al método `onDrawFrame()`. El método `onDrawFrame()` es un método público de la interfaz [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) de Android. En la aplicación de ejemplo para Android proporcionada por BytePlus, este método se utiliza en todos los fotogramas de la cámara y genera una textura. Al mismo tiempo, puede complementar el método `onDrawFrame()` con la lógica para convertir esta textura en un mapa de bits y enviarla a una fuente de entrada de imágenes personalizada. Como se muestra en el siguiente ejemplo de código, utilice el método `transferTextureToBitmap` proporcionado por el SDK de BytePlus para realizar esta conversión. Este método lo proporciona la biblioteca [com.bytedance.labcv.core.util.ImageUtil](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide#Appendix:%20convert%20input%20texture%20to%202D%20texture%20with%20upright%20face) del SDK de BytePlus Effects, como se muestra en el siguiente ejemplo de código. Luego, puede renderizar el `Surface` subyacente para Android de `CustomImageSource` al escribir el mapa de bits resultante en el lienzo de Surface. Muchas invocaciones sucesivas de `onDrawFrame()` dan como resultado una secuencia de mapas de bits y, cuando se combinan, crean una secuencia de video.

#### Java
<a name="integrating-byteplus-android-convert-to-bitmap-code"></a>

```
import com.bytedance.labcv.core.util.ImageUtil;
...
protected ImageUtil imageUtility;
...


@Override
public void onDrawFrame(GL10 gl10) {
  ...	
  // Convert BytePlus output to a Bitmap
  Bitmap outputBt = imageUtility.transferTextureToBitmap(output.getTexture(),ByteEffect     
  Constants.TextureFormat.Texture2D,output.getWidth(), output.getHeight());

  canvas = surface.lockCanvas(null);
  canvas.drawBitmap(outputBt, 0f, 0f, null);
  surface.unlockCanvasAndPost(canvas);
```

# Uso de DeepAR con el SDK de transmisión de IVS
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

Este documento explica cómo utilizar el SDK de DeepAR con el SDK de transmisión de IVS.

## Android
<a name="integrating-deepar-android"></a>

Consulte [Android Integration Guide de DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/) para obtener detalles sobre cómo integrar el SDK de DeepAR con el SDK de retransmisión de Android IVS.

## iOS
<a name="integrating-deepar-ios"></a>

Consulte la [Guía de integración de iOS de DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/) para obtener más información sobre cómo integrar el SDK de DeepAR con el SDK de transmisión IVS para iOS.

# Uso de Snap con el SDK de transmisión de IVS
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

Este documento explica cómo utilizar el SDK de Camera Kit de Snap con el SDK de transmisión de IVS.

## Web
<a name="integrating-snap-web"></a>

En esta sección se presupone que ya está familiarizado con la [publicación de videos y la suscripción a ellos mediante el SDK de transmisión web](getting-started-pub-sub-web.md).

Para integrar el SDK de Camera Kit de Snap con el SDK de transmisión Web en tiempo real de IVS, debe:

1. Instale el SDK y el Webpack de Camera Kit. (Nuestro ejemplo usa Webpack como paquete, pero puede usar cualquier paquete de su elección).

1. Cree `index.html`.

1. Agregue elementos de configuración.

1. Cree `index.css`.

1. Muestre y configure los participantes.

1. Muestre las cámaras y los micrófonos conectados.

1. Cree una sesión de Camera Kit.

1. Busque lentes y rellene el selector de lentes.

1. Renderice el resultado de una sesión de Camera Kit en un lienzo.

1. Cree una función para rellenar el menú desplegable de lentes.

1. Proporcione a Camera Kit una fuente multimedia para renderizar y publicar un`LocalStageStream`.

1. Cree `package.json`.

1. Cree un archivo de configuración de Webpack.

1. Configure un servidor HTTPS y realice una prueba.

A continuación, se describe cada uno de estos pasos.

### Instalación del SDK y del Webpack de Camera Kit
<a name="integrating-snap-web-install-camera-kit"></a>

En este ejemplo, utilizamos Webpack como nuestro empaquetador, pero puede usar cualquier otro.

```
npm i @snap/camera-kit webpack webpack-cli
```

### Creación del archivo index.html
<a name="integrating-snap-web-create-index"></a>

A continuación, cree la plantilla HTML e importe el SDK de transmisión web como una etiqueta script. En el código siguiente, asegúrese de reemplazar `<SDK version>` por la versión del SDK de transmisión que esté utilizando.

#### HTML
<a name="integrating-snap-web-create-index-code"></a>

```
<!--
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <title>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</title>

  <!-- Fonts and Styling -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css" />
  <link rel="stylesheet" href="./index.css" />

  <!-- Stages in Broadcast SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>
  <!-- Introduction -->
  <header>
    <h1>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</h1>

    <p>This sample is used to demonstrate basic HTML / JS usage. <b><a href="https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/multiple-hosts.html">Use the AWS CLI</a></b> to create a <b>Stage</b> and a corresponding <b>ParticipantToken</b>. Multiple participants can load this page and put in their own tokens. You can <b><a href="https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#glossary" target="_blank">read more about stages in our public docs.</a></b></p>
  </header>
  <hr />
  
  <!-- Setup Controls -->
 
  <!-- Display Local Participants -->
  
  <!-- Lens Selector -->

  <!-- Display Remote Participants -->

  <!-- Load All Desired Scripts -->
```

### Agregue elementos de configuración
<a name="integrating-snap-web-add-setup-elements"></a>

Cree el código HTML para seleccionar una cámara, un micrófono y un lente y para especificar un token de participante:

#### HTML
<a name="integrating-snap-web-setup-controls-code"></a>

```
<!-- Setup Controls -->
  <div class="row">
    <div class="column">
      <label for="video-devices">Select Camera</label>
      <select disabled id="video-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="audio-devices">Select Microphone</label>
      <select disabled id="audio-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="token">Participant Token</label>
      <input type="text" id="token" name="token" />
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="join-button">Join Stage</button>
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="leave-button">Leave Stage</button>
    </div>
  </div>
```

Añada un código HTML adicional debajo para mostrar las imágenes de las cámaras de los participantes locales y remotos:

#### HTML
<a name="integrating-snap-web-local-remote-participants-code"></a>

```
 <!-- Local Participant -->
<div class="row local-container">
    <canvas id="canvas"></canvas>

    <div class="column" id="local-media"></div>
    <div class="static-controls hidden" id="local-controls">
      <button class="button" id="mic-control">Mute Mic</button>
      <button class="button" id="camera-control">Mute Camera</button>
    </div>
  </div>

  
  <hr style="margin-top: 5rem"/>
  
  <!-- Remote Participants -->
  <div class="row">
    <div id="remote-media"></div>
  </div>
```

Cargue lógica adicional, incluidos los métodos de ayuda para configurar la cámara y el archivo JavaScript incluido. (Más adelante en esta sección, creará estos archivos JavaScript y los agrupará en un solo archivo para poder importar Camera Kit como un módulo. El archivo JavaScript agrupado contendrá la lógica para configurar Camera Kit, aplicar un lente y publicar la imagen de la cámara con un lente aplicado a un escenario). Agregue etiquetas de cierre a los elementos `body` y `html` para completar la creación del `index.html`.

#### HTML
<a name="integrating-snap-web-load-all-scripts-code"></a>

```
<!-- Load all Desired Scripts -->
  <script src="./helpers.js"></script>
  <script src="./media-devices.js"></script>
  <!-- <script type="module" src="./stages-simple.js"></script> -->
  <script src="./dist/bundle.js"></script>
</body>
</html>
```

### Creación del archivo index.css
<a name="integrating-snap-web-create-index-css"></a>

Cree un archivo de origen CSS para diseñar la página. No nos detendremos en este código, sino que nos centraremos en la lógica para administrar un escenario e integrarlo en el SDK de Camera Kit de Snap.

#### CSS
<a name="integrating-snap-web-create-index-css-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

html,
body {
  margin: 2rem;
  box-sizing: border-box;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
}

hr {
  margin: 1rem 0;
}

table {
  display: table;
}

canvas {
  margin-bottom: 1rem;
  background: green;
}

video {
  margin-bottom: 1rem;
  background: black;
  max-width: 100%;
  max-height: 150px;
}

.log {
  flex: none;
  height: 300px;
}

.content {
  flex: 1 0 auto;
}

.button {
  display: block;
  margin: 0 auto;
}

.local-container {
  position: relative;
}

.static-controls {
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  left: 0;
  right: 0;
  bottom: -4rem;
  text-align: center;
}

.static-controls button {
  display: inline-block;
}

.hidden {
  display: none;
}

.participant-container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin: 1rem;
}

video {
  border: 0.5rem solid #555;
  border-radius: 0.5rem;
}
.placeholder {
  background-color: #333333;
  display: flex;
  text-align: center;
  margin-bottom: 1rem;
}
.placeholder span {
  margin: auto;
  color: white;
}
#local-media {
  display: inline-block;
  width: 100vw;
}

#local-media video {
  max-height: 300px;
}

#remote-media {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
  width: 100%;
}

#lens-selector {
  width: 100%;
  margin-bottom: 1rem;
}
```

### Visualización y configuración de los participantes
<a name="integrating-snap-web-setup-participants"></a>

A continuación, cree `helpers.js`, que contiene métodos auxiliares que utilizará para mostrar y configurar los participantes:

#### JavaScript
<a name="integrating-snap-web-setup-participants-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

function setupParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);

  const participantContainerId = isLocal ? 'local' : id;
  const participantContainer = createContainer(participantContainerId);
  const videoEl = createVideoEl(participantContainerId);

  participantContainer.appendChild(videoEl);
  groupContainer.appendChild(participantContainer);

  return videoEl;
}

function teardownParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);
  const participantContainerId = isLocal ? 'local' : id;

  const participantDiv = document.getElementById(
    participantContainerId + '-container'
  );
  if (!participantDiv) {
    return;
  }
  groupContainer.removeChild(participantDiv);
}

function createVideoEl(id) {
  const videoEl = document.createElement('video');
  videoEl.id = id;
  videoEl.autoplay = true;
  videoEl.playsInline = true;
  videoEl.srcObject = new MediaStream();
  return videoEl;
}

function createContainer(id) {
  const participantContainer = document.createElement('div');
  participantContainer.classList = 'participant-container';
  participantContainer.id = id + '-container';

  return participantContainer;
}
```

### Visualización de las cámaras y los micrófonos conectados
<a name="integrating-snap-web-display-cameras-microphones"></a>

A continuación, cree `media-devices.js`, que contiene métodos auxiliares para mostrar las cámaras y los micrófonos conectados a su dispositivo:

#### JavaScript
<a name="integrating-snap-web-display-cameras-microphones-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

/**
 * Returns an initial list of devices populated on the page selects
 */
async function initializeDeviceSelect() {
  const videoSelectEl = document.getElementById('video-devices');
  videoSelectEl.disabled = false;

  const { videoDevices, audioDevices } = await getDevices();
  videoDevices.forEach((device, index) => {
    videoSelectEl.options[index] = new Option(device.label, device.deviceId);
  });

  const audioSelectEl = document.getElementById('audio-devices');

  audioSelectEl.disabled = false;
  audioDevices.forEach((device, index) => {
    audioSelectEl.options[index] = new Option(device.label, device.deviceId);
  });
}

/**
 * Returns all devices available on the current device
 */
async function getDevices() {
  // Prevents issues on Safari/FF so devices are not blank
  await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

  const devices = await navigator.mediaDevices.enumerateDevices();
  // Get all video devices
  const videoDevices = devices.filter((d) => d.kind === 'videoinput');
  if (!videoDevices.length) {
    console.error('No video devices found.');
  }

  // Get all audio devices
  const audioDevices = devices.filter((d) => d.kind === 'audioinput');
  if (!audioDevices.length) {
    console.error('No audio devices found.');
  }

  return { videoDevices, audioDevices };
}

async function getCamera(deviceId) {
  // Use Max Width and Height
  return navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
    audio: false,
  });
}

async function getMic(deviceId) {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
  });
}
```

### Creación de una sesión de Camera Kit
<a name="integrating-snap-web-camera-kit-session"></a>

Cree `stages.js`, que contiene la lógica para aplicar una lente a la transmisión de la cámara y publicar la transmisión en un escenario. Recomendamos copiar y pegar el siguiente bloque de código en `stages.js`. A continuación, puede revisar el código parte por parte para entender lo que sucede en las siguientes secciones.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);

  const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};

async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);

  // Create StageStreams for Audio and Video
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
      lensSelector.disabled = false;
    } else {
      controls.classList.add('hidden');
      lensSelector.disabled = true;
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

En la primera parte de este archivo, importamos el SDK de transmisión y el SDK web de Camera Kit e inicializamos las variables que usaremos con cada SDK. Para crear una sesión de Camera Kit, activamos `createSession` después de [impulsar el SDK web de Camera Kit](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Tenga en cuenta que un objeto de elemento de lienzo se pasa a una sesión; esto le indica a Camera Kit que lo renderice en ese lienzo.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code-2"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
```

### Búsqueda de lentes y rellenado del selector de lentes
<a name="integrating-snap-web-fetch-apply-lens"></a>

Para buscar sus lentes, remplace el marcador de posición del ID de grupo de lentes con el suyo, el cual puede encontrar en el [portal para desarrolladores de Camera Kit](https://camera-kit.snapchat.com/). Rellene el menú desplegable de selección de lentes con la función `populateLensSelector()` que crearemos más adelante.

#### JavaScript
<a name="integrating-snap-web-fetch-apply-lens-code"></a>

```
session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);
```

### Renderización del resultado de una sesión de Camera Kit en un lienzo
<a name="integrating-snap-web-render-output-to-canvas"></a>

Utilice el método [CaptureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) para devolver una `MediaStream` del contenido del lienzo. El lienzo contendrá una transmisión de video de la imagen de la cámara con una lente aplicada. Además, añada oyentes de eventos como botones para silenciar la cámara y el micrófono, así como oyentes de eventos para entrar y salir del escenario. En el oyente de eventos para unirse a un escenario, pasamos una sesión de Camera Kit y la `MediaStream` del lienzo para poder publicarla en un escenario.

#### JavaScript
<a name="integrating-snap-web-render-output-to-canvas-code"></a>

```
const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};
```

### Creación de una función para rellenar el menú desplegable de lentes
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Cree la siguiente función para rellenar el selector de **lentes** con los lentes que buscó anteriormente. El selector de **lentes** es un elemento de la interfaz de usuario de la página que permite elegir lentes de una lista y aplicarlos a la imagen de la cámara. Además, cree la función de devolución de llamada `handleLensChange` para aplicar el lente especificado cuando esté seleccionado en el menú desplegable de **lentes**.

#### JavaScript
<a name="integrating-snap-web-populate-lens-dropdown-code"></a>

```
const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};
```

### Proporcione a Camera Kit una fuente multimedia para renderizar y publique un LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Para publicar una transmisión de video con una lente aplicada, cree una función llamada `setCameraKitSource` para transferir la `MediaStream` capturada anteriormente desde el lienzo. La `MediaStream` desde el lienzo no sirve de nada por el momento porque aún no hemos incorporado nuestra cámara local. Podemos incorporar la señal de nuestra cámara local llamando al método auxiliar `getCamera` y asignándolo a `localCamera`. Luego, podemos pasar la señal de nuestra cámara local (vía`localCamera`) y el objeto de sesión a `setCameraKitSource`. La función `setCameraKitSource` convierte la señal de nuestra cámara local en una [fuente de contenido multimedia para Camera Kit](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource) con solo una llamada a `createMediaStreamSource`. La fuente multimedia para `CameraKit` se [transforma](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms) para reflejar la cámara frontal. El efecto de la lente se aplica a la fuente multimedia y se renderiza en el lienzo de salida mediante una llamada`session.play()`.

Ahora que la lente está aplicada a la `MediaStream` capturada desde el lienzo, podemos proceder a publicarla en un escenario. Para ello, creamos un `LocalStageStream` con las pistas de video de `MediaStream`. Luego, se puede pasar una instancia de `LocalStageStream` a `StageStrategy` para publicarla.

#### JavaScript
<a name="integrating-snap-web-publish-localstagestream-code"></a>

```
async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);
  // Create StageStreams for Audio and Video
  // cameraStageStream = new LocalStageStream(localCamera.getVideoTracks()[0]);
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };
```

El código restante que aparece a continuación sirve para crear y gestionar nuestro escenario:

#### JavaScript
<a name="integrating-snap-web-create-manage-stage-code"></a>

```
stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events

  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
    } else {
      controls.classList.add('hidden');
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

### Creación del archivo package.json
<a name="integrating-snap-web-package-json"></a>

Cree el archivo `package.json` y agregue la siguiente configuración de JSON. Este archivo define nuestras dependencias e incluye un comando de script para agrupar nuestro código.

#### Configuración de JSON
<a name="integrating-snap-web-package-json-code"></a>

```
{
  "dependencies": {
    "@snap/camera-kit": "^0.10.0"
  },
  "name": "ivs-stages-with-snap-camerakit",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4"
  }
}
```

### Creación de un archivo de configuración de Webpack
<a name="integrating-snap-web-webpack-config"></a>

Cree `webpack.config.js` y agregue el siguiente código. Esto agrupa el código que hemos creado hasta ahora para que podamos usar la declaración de importación para usar Camera Kit.

#### JavaScript
<a name="integrating-snap-web-webpack-config-code"></a>

```
const path = require('path');
module.exports = {
  entry: ['./stage.js'],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
```

Por último, ejecute `npm run build` para agrupar su JavaScript como se define en el archivo de configuración de Webpack. Para la prueba, puede servir a HTML y JavaScript desde su computadora local. En este ejemplo, utilizamos el módulo `http.server` de Python. 

### Configuración y prueba de un servidor HTTPS
<a name="integrating-snap-web-https-server-test"></a>

Para probar nuestro código, debemos configurar un servidor HTTPS. Usar un servidor HTTPS en el desarrollo local y las pruebas para integrar la aplicación web en el SDK de Camera Kit de Snap ayudará a evitar problemas relacionados con el CORS (intercambio de recursos entre orígenes).

Abra un terminal y vaya hasta el directorio en que creó todo el código hasta este punto. Use el siguiente comando para generar un certificado SSL/TLS autofirmado y una clave privada:

```
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```

Esto crea dos archivos: `key.pem` (la clave privada) y `cert.pem` (el certificado autofirmado). Cree un nuevo archivo de Python, denominado `https_server.py`, y agregue el siguiente código:

#### Python
<a name="integrating-snap-web-https-server-test-code"></a>

```
import http.server
import ssl

# Set the directory to serve files from
DIRECTORY = '.'

# Create the HTTPS server
server_address = ('', 4443)
httpd = http.server.HTTPServer(
    server_address, http.server.SimpleHTTPRequestHandler)

# Wrap the socket with SSL/TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('cert.pem', 'key.pem')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

print(f'Starting HTTPS server on https://localhost:4443, serving {DIRECTORY}')
httpd.serve_forever()
```

Abra una terminal, navegue hasta el directorio donde creó el archivo `https_server.py` y ejecute el siguiente comando:

```
python3 https_server.py
```

Esto inicia el servidor HTTPS en https://localhost:4443 y entrega archivos del directorio actual. Asegúrese de que los archivos `cert.pem` y `key.pem` se encuentren en el mismo directorio que el archivo `https_server.py`.

Abra el navegador y navegue hasta https://localhost:4443. Como se trata de un certificado SSL/TLS autofirmado, el navegador web no confiará en él, por lo que recibirá una advertencia. Como esto es solo para fines de prueba, puede omitir la advertencia. A continuación, debería ver en pantalla el efecto AR de la lente de Snap que especificó anteriormente aplicado a la transmisión de la cámara.

Tenga en cuenta que esta configuración, que utiliza los módulos integrados `http.server` y `ssl` los módulos de Python, es adecuada para el desarrollo y las pruebas locales, pero no se recomienda para un entorno de producción. Los navegadores web y otros clientes no confían en el certificado SSL/TLS autofirmado que se usa en esta configuración, lo que significa que los usuarios encontrarán advertencias de seguridad al acceder al servidor. Además, aunque en este ejemplo usamos los módulos http.server y ssl integrados en Python, puede optar por utilizar otra solución de servidor HTTPS.

## Android
<a name="integrating-snap-android"></a>

Para integrar el SDK de Camera Kit de Snap con el SDK de transmisión para Android de IVS, debe instalar el SDK de Camera Kit, inicializar una sesión de Camera Kit, aplicar una lente y enviar el resultado de la sesión de Camera Kit a la fuente de entrada de la imagen personalizada.

Para instalar el SDK de Camera Kit, añada lo siguiente al archivo `build.gradle` de su módulo. Sustituya `$cameraKitVersion` por la [última versión del SDK del Camera Kit](https://docs.snap.com/camera-kit/integrate-sdk/mobile/changelog-mobile).

### Java
<a name="integrating-snap-android-install-camerakit-sdk-code"></a>

```
implementation "com.snap.camerakit:camerakit:$cameraKitVersion"
```

Inicialice y obtenga un `cameraKitSession`. Camera Kit también proporciona un práctico contenedor para las API [CameraX](https://developer.android.com/media/camera/camerax) de Android, por lo que no tiene que escribir una lógica complicada para usar CameraX con Camera Kit. Puede usar el objeto `CameraXImageProcessorSource` como [fuente](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) para [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html), que le permite iniciar fotogramas de transmisión con vistas previas de la cámara.

### Java
<a name="integrating-snap-android-initialize-camerakitsession-code"></a>

```
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // Camera Kit support implementation of ImageProcessor that is backed by CameraX library:
        // https://developer.android.com/training/camerax
        CameraXImageProcessorSource imageProcessorSource = new CameraXImageProcessorSource( 
            this /*context*/, this /*lifecycleOwner*/
        );
        imageProcessorSource.startPreview(true /*cameraFacingFront*/);

        cameraKitSession = Sessions.newBuilder(this)
                .imageProcessorSource(imageProcessorSource)
                .attachTo(findViewById(R.id.camerakit_stub))
                .build();
    }
```

### Búsqueda y aplicación de lentes
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Puede configurar las lentes y su orden en el carrusel del [portal para desarrolladores de Camera Kit](https://camera-kit.snapchat.com/):

#### Java
<a name="integrating-snap-android-configure-lenses-code"></a>

```
// Fetch lenses from repository and apply them
 // Replace LENS_GROUP_ID with Lens Group ID from https://camera-kit.snapchat.com
cameraKitSession.getLenses().getRepository().get(new Available(LENS_GROUP_ID), available -> {
     Log.d(TAG, "Available lenses: " + available);
     Lenses.whenHasFirst(available, lens -> cameraKitSession.getLenses().getProcessor().apply(lens, result -> {
          Log.d(TAG,  "Apply lens [" + lens + "] success: " + result);
      }));
});
```

Para transmitir, envíe los fotogramas procesados al `Surface` subyacente de una fuente de imagen personalizada. Use un objeto `DeviceDiscovery` y cree un `CustomImageSource` para devolver un`SurfaceSource`. A continuación, puede renderizar el resultado de una sesión de `CameraKit` en el `Surface` subyacente proporcionado por el`SurfaceSource`.

#### Java
<a name="integrating-snap-android-broadcast-code"></a>

```
val publishStreams = ArrayList<LocalStageStream>()

val deviceDiscovery = DeviceDiscovery(applicationContext)
val customSource = deviceDiscovery.createImageInputSource(BroadcastConfiguration.Vec2(720f, 1280f))

cameraKitSession.processor.connectOutput(outputFrom(customSource.inputSurface))
val customStream = ImageLocalStageStream(customSource)

// After rendering the output from a Camera Kit session to the Surface, you can 
// then return it as a LocalStageStream to be published by the Broadcast SDK
val customStream: ImageLocalStageStream = ImageLocalStageStream(surfaceSource)
publishStreams.add(customStream)

@Override
fun stageStreamsToPublishForParticipant(stage: Stage, participantInfo: ParticipantInfo): List<LocalStageStream> = publishStreams
```

# Uso del reemplazo de fondo con el SDK de transmisión de IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

El reemplazo del fondo es un tipo de filtro de cámara que permite a los creadores de transmisiones en directo cambiar sus fondos. Como se muestra en el diagrama siguiente, reemplazar el fondo implica:

1. Obtener una imagen de cámara a partir de la transmisión de la cámara en directo.

1. Segmentarla en componentes de primer plano y segundo plano con el ML Kit de Google.

1. Combinar la máscara de segmentación resultante con una imagen de fondo personalizada.

1. Pasarla a una fuente de imagen personalizada para su transmisión.

![\[Flujo de trabajo para implementar el reemplazo del fondo.\]](http://docs.aws.amazon.com/es_es/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## Web
<a name="background-replacement-web"></a>

En esta sección se presupone que ya está familiarizado con la [publicación de videos y la suscripción a ellos mediante el SDK de transmisión web](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Para sustituir el fondo de una transmisión en directo por una imagen personalizada, utilice el [modelo de segmentación de selfies](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) con [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Se trata de un modelo de machine learning que identifica qué píxeles del fotograma de video están en primer plano o en segundo plano. Puede utilizar los resultados del modelo para sustituir el fondo de una transmisión en directo copiando los píxeles de primer plano de la transmisión de video a una imagen personalizada que represente el nuevo fondo.

Para integrar el reemplazo del fondo de pantalla con el SDK de transmisión web en tiempo real de IVS, debe:

1. Instalar MediaPipe y Webpack. (Nuestro ejemplo usa Webpack como paquete, pero puede usar cualquier paquete de su elección).

1. Cree `index.html`.

1. Agregue elementos multimedia.

1. Añada una etiqueta de script.

1. Cree `app.js`.

1. Cargue una imagen de fondo personalizada.

1. Cree una instancia de `ImageSegmenter`.

1. Renderice la transmisión de video en un lienzo.

1. Cree una lógica de reemplazo de fondo.

1. Cree el archivo de configuración de Webpack.

1. Agrupe su archivo JavaScript.

### Instalación de MediaPipe y Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Para empezar, instale `@mediapipe/tasks-vision` y los paquetes npm de `webpack`. El siguiente ejemplo usa Webpack como un empaquetador de JavaScript; puede usar un empaquetador diferente si lo prefiere.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Asegúrese de actualizar también su `package.json` para especificar `webpack` al desarrollar script:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Creación del archivo index.html
<a name="background-replacement-web-create-index"></a>

A continuación, cree la plantilla HTML e importe el SDK de transmisión web como una etiqueta script. En el código siguiente, asegúrese de reemplazar `<SDK version>` por la versión del SDK de transmisión que esté utilizando.

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Import the SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

</body>
</html>
```

### Agregue elementos multimedia
<a name="background-replacement-web-add-media-elements"></a>

A continuación, agregue un elemento de video y dos elementos de lienzo dentro de la etiqueta corporal. El elemento de video contendrá las imágenes de la cámara en directo y se utilizará como entrada para el segmentador de imágenes de MediaPipe. El primer elemento de lienzo se utilizará para obtener una vista previa de la transmisión que se emitirá. El segundo elemento de lienzo se utilizará para renderizar la imagen personalizada que se utilizará como fondo. Como el segundo lienzo con la imagen personalizada solo se usa como fuente para copiar píxeles mediante programación al lienzo final, está oculto a la vista.

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### Agregue una etiqueta de script
<a name="background-replacement-web-add-script-tag"></a>

Agregue una etiqueta de script para cargar un archivo JavaScript agrupado que contendrá el código para reemplazar el fondo y publicarlo en un escenario:

```
<script src="./dist/bundle.js"></script>
```

### Creación de app.js
<a name="background-replacement-web-create-appjs"></a>

A continuación, cree un archivo JavaScript para obtener los objetos de elemento para los elementos de lienzo y video que se crearon en la página HTML. Importe los módulos `ImageSegmenter` y `FilesetResolver`. El módulo `ImageSegmenter` se utilizará para realizar la tarea de segmentación.

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

A continuación, cree una función llamada `init()` para recuperar el MediaStream de la cámara del usuario e invoque una función de devolución de llamada cada vez que termine de cargarse el fotograma de la cámara. Añada detectores de eventos para que los botones se unan y salgan de un escenario.

Tenga en cuenta que cuando nos unimos a un escenario, pasamos una variable llamada`segmentationStream`. Se trata de una transmisión de video capturada desde un elemento de lienzo, que contiene una imagen de primer plano superpuesta a la imagen personalizada que representa el fondo. Más adelante, esta transmisión personalizada se utilizará para crear una instancia de `LocalStageStream`, que se podrá publicar en un escenario.

#### JavaScript
<a name="create-appjs-create-init-code"></a>

```
const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });
};
```

### Carga de una imagen de fondo personalizada
<a name="background-replacement-web-background-image"></a>

En la parte inferior de la función `init`, añada código para llamar a una función llamada`initBackgroundCanvas`, que carga una imagen personalizada de un archivo local y la renderiza en un lienzo. Definiremos esta función en el siguiente paso. Asigne la `MediaStream` recuperada de la cámara del usuario al objeto de video. Más tarde, este objeto de video se pasará al segmentador de imágenes. Además, configure una función denominada `renderVideoToCanvas` como la función de devolución de llamada para que se invoque cada vez que un fotograma de video termine de cargarse. Definiremos esta función en un paso posterior.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Vamos a implementar la función `initBackgroundCanvas`, que carga una imagen desde un archivo local. En este ejemplo, utilizaremos una imagen de una playa como fondo personalizado. El lienzo que contiene la imagen personalizada se ocultará de la pantalla, ya que lo combinará con los píxeles del primer plano del elemento del lienzo que contiene la imagen de la cámara.

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### Creación de una instancia de ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

A continuación, cree una instancia de `ImageSegmenter`, que segmentará la imagen y devolverá el resultado en forma de máscara. Al crear una instancia de `ImageSegmenter`, utilizará el [modelo de segmentación de selfies](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model).

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### Renderice la transmisión de video en un lienzo
<a name="background-replacement-web-render-video-to-canvas"></a>

A continuación, cree la función que renderice la transmisión de video al otro elemento del lienzo. Necesitamos renderizar la transmisión de video en un lienzo para poder extraer los píxeles del primer plano utilizando la API Canvas 2D. Mientras lo hacemos, también pasaremos un fotograma de video a nuestra instancia`ImageSegmenter`, utilizando el método [segmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) para segmentar el primer plano del fondo del fotograma de video. Cuando el método [segmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) regrese, invoque nuestra función de devolución de llamada personalizada, `replaceBackground`, para reemplazar el fondo.

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### Creación de una lógica de reemplazo en segundo plano
<a name="background-replacement-web-logic"></a>

Cree la función `replaceBackground`, que fusiona la imagen de fondo personalizada con el primer plano de la transmisión de la cámara para reemplazar el fondo. La función recupera primero los datos de píxeles subyacentes de la imagen de fondo personalizada y la transmisión de video de los dos elementos del lienzo creados anteriormente. A continuación, recorre en iteración la máscara proporcionada por`ImageSegmenter`, que indica qué píxeles están en primer plano. A medida que recorre la máscara, copia de forma selectiva los píxeles que contienen la imagen de la cámara del usuario en los datos de píxeles de fondo correspondientes. Una vez hecho esto, convierte los datos de píxeles finales con el primer plano copiado en el fondo y los dibuja en un lienzo.

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

Como referencia, aquí está el archivo `app.js` completo que contiene toda la lógica anterior:

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

let cameraButton = document.getElementById("camera-control");
let micButton = document.getElementById("mic-control");
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

let controls = document.getElementById("local-controls");
let audioDevicesList = document.getElementById("audio-devices");
let videoDevicesList = document.getElementById("video-devices");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

const joinStage = async (segmentationStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById("token").value;

  if (!token) {
    window.alert("Please enter a participant token");
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove("hidden");
    } else {
      controls.classList.add("hidden");
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log("Participant Joined:", participant);
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    console.log("Participant Media Added: ", participant, streams);

    let streamsToDisplay = streams;

    if (participant.isLocal) {
      // Ensure to exclude local audio streams, otherwise echo will occur
      streamsToDisplay = streams.filter((stream) => stream.streamType === StreamType.VIDEO);
    }

    const videoEl = setupParticipant(participant);
    streamsToDisplay.forEach((stream) => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log("Participant Left: ", participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = "Hide Camera";
  micButton.innerText = "Mute Mic";
  controls.classList.add("hidden");
};

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Creación de un archivo de configuración de Webpack
<a name="background-replacement-web-webpack-config"></a>

Agregue esta configuración a su archivo de configuración de Webpack para empaquetar`app.js`, de modo que las llamadas de importación funcionen:

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

```
const path = require("path");
module.exports = {
  entry: ["./app.js"],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
```

### Agrupación de sus archivos JavaScript
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Inicie un servidor HTTP simple desde el directorio que contiene `index.html` y abra `localhost:8000` para ver el resultado:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Para reemplazar el fondo de tu transmisión en directo, puede usar la API de segmentación de selfies del [ML Kit de Google](https://developers.google.com/ml-kit/vision/selfie-segmentation). La API de segmentación de selfies acepta una imagen de cámara como entrada y devuelve una máscara que proporciona una puntuación de confianza para cada píxel de la imagen, que indica si estaba en primer plano o en segundo plano. En función de la puntuación de confianza, puede recuperar el color de píxel correspondiente de la imagen de fondo o de la imagen de primer plano. Este proceso continúa hasta que se hayan examinado todas las puntuaciones de confianza de la máscara. El resultado es una nueva matriz de colores de píxeles que contiene los píxeles del primer plano combinados con los píxeles de la imagen de fondo.

Para integrar la sustitución en segundo plano con el SDK de retransmisión para transmisión en tiempo real de IVS:

1. Instale las bibliotecas CameraX y el kit ML de Google.

1. Inicialice las variables repetitivas.

1. Cree de una fuente de imágenes personalizada.

1. Administre los fotogramas de las cámaras.

1. Transfiera los marcos de las cámaras al ML Kit de Google.

1. Superponga el primer plano del marco de la cámara sobre su fondo personalizado.

1. Introduzca la nueva imagen en una fuente de imágenes personalizada.

### Instalación de las bibliotecas CameraX y del kit ML de Google
<a name="background-replacement-android-install-camerax-googleml"></a>

Para extraer imágenes de la transmisión de la cámara en vivo, use la biblioteca CameraX de Android. Para instalar la biblioteca CameraX y el kit ML de Google, añada lo siguiente al archivo `build.gradle` del módulo. Sustituya `${camerax_version}` y `${google_ml_kit_version}` por la última versión de las bibliotecas [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) y [ML Kit Google](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), respectivamente. 

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

Importe las siguientes bibliotecas:

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### Inicialización de variables reutilizables
<a name="background-replacement-android-initialize-variables"></a>

Inicialice una instancia de `ImageAnalysis` y una instancia de `ExecutorService`:

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

[Inicialice una instancia en STREAM\$1MODE](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode):

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### Creación de una fuente de imágenes personalizada
<a name="background-replacement-android-create-image-source"></a>

En el método `onCreate` de su actividad, cree una instancia de un objeto `DeviceDiscovery` y cree una fuente de imágenes personalizada. El `Surface` proporcionado por la fuente de imagen personalizada recibirá la imagen final, con el primer plano superpuesto sobre una imagen de fondo personalizada. A continuación, creará una instancia de `ImageLocalStageStream` utilizando la fuente de imagen personalizada. Luego, la instancia de `ImageLocalStageStream` (nombrada `filterStream` en este ejemplo) puede publicarse en un escenario. Consulte la [Guía del SDK de transmisión para Android de IVS](broadcast-android.md) para obtener instrucciones sobre cómo configurar un escenario. Por último, cree también un hilo que se utilizará para gestionar la cámara.

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)

cameraExecutor = Executors.newSingleThreadExecutor()
```

### Administración de fotogramas de cámara
<a name="background-replacement-android-camera-frames"></a>

A continuación, cree una función para inicializar la cámara. Esta función utiliza la biblioteca CameraX para extraer imágenes de la transmisión de la cámara en directo. En primer lugar, se crea una instancia de `ProcessCameraProvider` llamada `cameraProviderFuture`. Este objeto representa un resultado futuro de la obtención de un proveedor de cámaras. A continuación, cargue una imagen del proyecto en forma de mapa de bits. En este ejemplo se utiliza la imagen de una playa como fondo, pero puede ser cualquier imagen que desee.

A continuación, añada un oyente a `cameraProviderFuture`. Este oyente recibe una notificación cuando la cámara está disponible o si se produce un error durante el proceso de obtención de un proveedor de cámaras.

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

En el oyente, cree `ImageAnalysis.Builder` para acceder a cada fotograma individual de la transmisión de la cámara en directo. Establezca la estrategia de contrapresión para `STRATEGY_KEEP_ONLY_LATEST`. Esto garantiza que solo se entregue un cuadro de cámara a la vez para su procesamiento. Convierta cada fotograma de cámara individual en un mapa de bits, de forma que pueda extraer sus píxeles y luego combinarlos con la imagen de fondo personalizada.

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### Transferencia de los marcos de cámara al ML Kit de Google
<a name="background-replacement-android-frames-to-mlkit"></a>

A continuación, cree una `InputImage` y pásela a la instancia de Segmenter para su procesamiento. Se puede crear una `InputImage` a partir de una `ImageProxy` proporcionada por la instancia de `ImageAnalysis`. Una vez que se proporciona una `InputImage` a Segmenter, devuelve una máscara con puntuaciones de confianza que indican la probabilidad de que un píxel esté en primer plano o en segundo plano. Esta máscara también proporciona propiedades de ancho y alto, que utilizará para crear una nueva matriz que contenga los píxeles de fondo de la imagen de fondo personalizada cargada anteriormente.

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### Superposición del primer plano del marco de la cámara sobre el fondo personalizado
<a name="background-replacement-android-overlay-frame-foreground"></a>

Con la máscara que contiene las puntuaciones de confianza, el marco de la cámara como mapa de bits y los píxeles de color de la imagen de fondo personalizada, tiene todo lo que necesita para superponer el primer plano al fondo personalizado. A continuación, se invoca la función `overlayForeground` con los siguientes parámetros:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Esta función recorre la máscara y comprueba los valores de confianza para determinar si se debe obtener el color de píxel correspondiente de la imagen de fondo o del marco de la cámara. Si el valor de confianza indica que lo más probable es que un píxel de la máscara esté en segundo plano, obtendrá el color de píxel correspondiente de la imagen de fondo; de lo contrario, obtendrá el color de píxel correspondiente del marco de la cámara para crear el primer plano. Una vez que la función termine de recorrer la máscara, se crea un nuevo mapa de bits con la nueva matriz de píxeles de color y se devuelve. Este nuevo mapa de bits contiene el primer plano superpuesto sobre el fondo personalizado.

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### Introducción de la nueva imagen en una fuente de imagen personalizada
<a name="background-replacement-android-custom-image-source"></a>

A continuación, puede escribir el nuevo mapa de bits en el `Surface` proporcionado por una fuente de imagen personalizada. Esto se transmitirá a su escenario.

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

Esta es la función completa para obtener los fotogramas de la cámara, pasarlos a Segmenter y superponerlos sobre el fondo:

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```