

# SDK de transmisión de IVS: orígenes de audio personalizados \| Transmisión en tiempo real
<a name="broadcast-custom-audio-sources"></a>

**Nota:** Esta guía solo se aplica al SDK de transmisión de Android de transmisión en tiempo real con IVS. La información sobre los SDK para iOS y la web se publicará en el futuro.

Los orígenes de entrada de audio personalizados permiten que una aplicación proporcione su propia entrada de audio al SDK de transmisión, en vez de limitarse al micrófono integrado del dispositivo. Un origen de audio personalizado permite a las aplicaciones transmitir audio procesado con efectos, mezclar varias transmisiones de audio o integrarse con bibliotecas de procesamiento de audio de terceros.

Cuando utiliza un origen de entrada de imagen, el SDK de transmisión deja de ser responsable de la administración directa del micrófono. En su lugar, su aplicación es responsable de capturar, procesar y enviar los datos de audio al origen personalizado.

El flujo de trabajo del origen de audio personalizado sigue estos pasos:

1. Entrada de audio: cree un origen de audio personalizado con un formato de audio específico (frecuencia de muestreo, canales, formato). 

1. Su procesamiento: capture o genere datos de audio a partir de su canalización de procesamiento de audio.

1. Origen de audio personalizado: envíe los búferes de audio al origen personalizado mediante `appendBuffer()`.

1. Escenario: encapsúlelo en `LocalStageStream` y publíquelo en el escenario a través de su `StageStrategy`. 

1. Participantes: los participantes del escenario reciben el audio procesado en tiempo real.

## Android
<a name="custom-audio-sources-android"></a>

### Creación de un origen de audio personalizado
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Después de crear una sesión de `DeviceDiscovery`, cree un origen de entrada de audio:

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context); 
 
// Create custom audio source with specific format 
CustomAudioSource customAudioSource = deviceDiscovery.createAudioInputSource( 
   2,  // Number of channels (1 = mono, 2 = stereo) 
   BroadcastConfiguration.AudioSampleRate.RATE_48000,  // Sample rate 
   AudioDevice.Format.INT16  // Audio format (16-bit PCM) 
);
```

Este método devuelve un `CustomAudioSource`, que acepta datos de audio PCM sin procesar. El origen de audio personalizado debe configurarse con el mismo formato de audio que produce la canalización de procesamiento de audio.

#### Formatos admitidos de audio
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parámetro | Opciones | Descripción | 
| --- | --- | --- | 
| Canales | 1 (mono), 2 (estéreo) | Número de canales de audio. | 
| Velocidad de muestreo | RATE\_16000, RATE\_44100, RATE\_48000 | Frecuencia de muestreo de audio en Hz. Se recomienda 48 kHz para una alta calidad. | 
| Formato | INT16, FLOAT32 | Formato de muestra de audio. INT16 es un PCM de punto fijo de 16 bits, y FLOAT32 es un PCM de punto flotante de 32 bits. Están disponibles los formatos intercalado y plano. | 

### Envío de datos de audio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Para enviar datos de audio al origen personalizado, utilice el método `appendBuffer()`:

```
// Prepare audio data in a ByteBuffer 
ByteBuffer audioBuffer = ByteBuffer.allocateDirect(bufferSize); 
audioBuffer.put(pcmAudioData);  // Your processed audio data 
 
// Calculate the number of bytes 
long byteCount = pcmAudioData.length; 
 
// Submit audio to the custom source 
// presentationTimeUs should be generated by and come from your audio source
int samplesProcessed = customAudioSource.appendBuffer( 
   audioBuffer, 
   byteCount, 
   presentationTimeUs 
); 
 
if (samplesProcessed > 0) { 
   Log.d(TAG, "Successfully submitted " + samplesProcessed + " samples"); 
} else { 
   Log.w(TAG, "Failed to submit audio samples"); 
} 
 
// Clear buffer for reuse 
audioBuffer.clear();
```

**Consideraciones importantes:**
+ Los datos de audio deben estar en el formato especificado al crear el origen personalizado.
+ Las marcas de tiempo deben aumentar de forma monótona y ser proporcionadas por su fuente de audio para garantizar una reproducción de audio fluida.
+ Envíe el audio con regularidad para evitar interrupciones en la transmisión.
+ El método devuelve el número de muestras procesadas (0 indica un error). 

### Publicación en un escenario
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Encapsule `CustomAudioSource` en un `AudioLocalStageStream` y devuélvalo desde su `StageStrategy`:

```
// Create the audio stream from custom source 
AudioLocalStageStream audioStream = new AudioLocalStageStream(customAudioSource); 
 
// Define your stage strategy 
Strategy stageStrategy = new Strategy() { 
   @NonNull 
   @Override 
   public List<LocalStageStream> stageStreamsToPublishForParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      List<LocalStageStream> streams = new ArrayList<>(); 
      streams.add(audioStream);  // Publish custom audio 
      return streams; 
   } 
 
   @Override 
   public boolean shouldPublishFromParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return true;  // Control when to publish 
   } 
 
   @Override 
   public Stage.SubscribeType shouldSubscribeToParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return Stage.SubscribeType.AUDIO_VIDEO; 
   } 
}; 
 
// Create and join the stage 
Stage stage = new Stage(context, stageToken, stageStrategy);
```

### Ejemplo completo: integración de procesamiento de audio
<a name="custom-audio-sources-android-complete-example"></a>

Este es un ejemplo completo que muestra la integración con un SDK de procesamiento de audio:

```
public class AudioStreamingActivity extends AppCompatActivity { 
   private DeviceDiscovery deviceDiscovery; 
   private CustomAudioSource customAudioSource; 
   private AudioLocalStageStream audioStream; 
   private Stage stage; 
 
   @Override 
   protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
 
      // Configure audio manager 
      StageAudioManager.getInstance(this) 
         .setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT); 
 
      // Initialize IVS components 
      initializeIVSStage(); 
 
      // Initialize your audio processing SDK 
      initializeAudioProcessing(); 
   } 
 
   private void initializeIVSStage() { 
      deviceDiscovery = new DeviceDiscovery(this); 
 
      // Create custom audio source (48kHz stereo, 16-bit) 
      customAudioSource = deviceDiscovery.createAudioInputSource( 
         2,  // Stereo 
         BroadcastConfiguration.AudioSampleRate.RATE_48000, 
         AudioDevice.Format.INT16 
      ); 
 
      // Create audio stream 
      audioStream = new AudioLocalStageStream(customAudioSource); 
 
      // Create stage with strategy 
      Strategy strategy = new Strategy() { 
         @NonNull 
         @Override 
         public List<LocalStageStream> stageStreamsToPublishForParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Collections.singletonList(audioStream); 
         } 
 
         @Override 
         public boolean shouldPublishFromParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return true; 
         } 
 
         @Override 
         public Stage.SubscribeType shouldSubscribeToParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Stage.SubscribeType.AUDIO_VIDEO; 
         } 
      }; 
 
      stage = new Stage(this, getStageToken(), strategy); 
   } 
 
   private void initializeAudioProcessing() { 
      // Initialize your audio processing SDK 
      // Set up callback to receive processed audio 
      yourAudioSDK.setAudioCallback(new AudioCallback() { 
         @Override 
         public void onProcessedAudio(byte[] audioData, int sampleRate, 
                                     int channels, long timestamp) { 
            // Submit processed audio to IVS Stage 
            submitAudioToStage(audioData, timestamp); 
         } 
      }); 
   } 
 
   // The timestamp is required to come from your audio source and you  
   // should not be generating one on your own, unless your audio source 
   // does not provide one. If that is the case, create your own epoch  
   // timestamp and manually calculate the duration between each sample  
   // using the number of frames and frame size. 

   private void submitAudioToStage(byte[] audioData, long timestamp) { 
      try { 
         // Allocate direct buffer 
         ByteBuffer buffer = ByteBuffer.allocateDirect(audioData.length); 
         buffer.put(audioData); 
 
         // Submit to custom audio source 
         int samplesProcessed = customAudioSource.appendBuffer( 
            buffer, 
            audioData.length, 
            timestamp > 0 ? timestamp : System.nanoTime() / 1000 
         ); 
 
         if (samplesProcessed <= 0) { 
            Log.w(TAG, "Failed to submit audio samples"); 
         } 
 
         buffer.clear(); 
      } catch (Exception e) { 
         Log.e(TAG, "Error submitting audio: " + e.getMessage(), e); 
      } 
   } 
 
   @Override 
   protected void onDestroy() { 
      super.onDestroy(); 
      if (stage != null) { 
          stage.release(); 
      } 
   } 
}
```

### Prácticas recomendadas
<a name="custom-audio-sources-android-best-practices"></a>

#### Consistencia de formato de audio
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Asegúrese de que el formato de audio que envíe coincida con el formato especificado al crear el origen personalizado:

```
// If you create with 48kHz stereo INT16 
customAudioSource = deviceDiscovery.createAudioInputSource( 
   2, RATE_48000, INT16 
); 
 
// Your audio data must be: 
// - 2 channels (stereo) 
// - 48000 Hz sample rate 
// - 16-bit interleaved PCM format
```

#### Administración de búfer
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

Use `ByteBuffers` directamente y reutilícelos para minimizar la recopilación de elementos no utilizados: 

```
// Allocate once 
private ByteBuffer audioBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); 
 
// Reuse in callback 
public void onAudioData(byte[] data) { 
   audioBuffer.clear(); 
   audioBuffer.put(data); 
   customAudioSource.appendBuffer(audioBuffer, data.length, getTimestamp()); 
   audioBuffer.clear(); 
}
```

#### Sincronización y temporización
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Debe utilizar las marcas de tiempo proporcionadas por el origen de audio para que la reproducción del audio sea fluida. Si el origen de audio no proporciona su propia marca de tiempo, cree su propia marca de tiempo de época y calcule de forma manual la duración entre cada muestra mediante el número de fotogramas y el tamaño del fotograma. 

```
// "audioFrameTimestamp" should be generated by your audio source
// Consult your audio source’s documentation for information on how to get this 
long timestamp = audioFrameTimestamp;
```

#### Gestión de errores
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Compruebe siempre el valor de retorno de `appendBuffer()`: 

```
int samplesProcessed = customAudioSource.appendBuffer(buffer, count, timestamp); 
 
if (samplesProcessed <= 0) { 
   Log.w(TAG, "Audio submission failed - buffer may be full or format mismatch"); 
   // Handle error: check format, reduce submission rate, etc. 
}
```