SDK de transmisión de IVS: orígenes de audio personalizados | Transmisión en tiempo real
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:
-
Entrada de audio: cree un origen de audio personalizado con un formato de audio específico (frecuencia de muestreo, canales, formato).
-
Su procesamiento: capture o genere datos de audio a partir de su canalización de procesamiento de audio.
-
Origen de audio personalizado: envíe los búferes de audio al origen personalizado mediante
appendBuffer(). -
Escenario: encapsúlelo en
LocalStageStreamy publíquelo en el escenario a través de suStageStrategy. -
Participantes: los participantes del escenario reciben el audio procesado en tiempo real.
Android
Creación de un origen de audio personalizado
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
| 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
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
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
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
Consistencia de formato de audio
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
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
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
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. }