IVS Broadcast SDK: Custom Audio Sources | Real-Time Streaming - Amazon IVS

IVS Broadcast SDK: Custom Audio Sources | Real-Time Streaming

Note: This guide only applies to the IVS real-time streaming Android broadcast SDK. Information for the iOS and web SDKs will be published in the future.

Custom audio-input sources allow an application to provide its own audio input to the broadcast SDK, instead of being limited to the device’s built-in microphone. A custom audio source enables applications to stream processed audio with effects, mix multiple audio streams, or integrate with third-party audio processing libraries.

When you use a custom audio-input source, the broadcast SDK is no longer responsible for managing the microphone directly. Instead, your application is responsible for capturing, processing, and submitting audio data to the custom source.

The custom-audio-source workflow follows these steps:

  1. Audio input — Create a custom audio source with specified audio format (sample rate, channels, format).

  2. Your processing — Capture or generate audio data from your audio processing pipeline.

  3. Custom audio source — Submit audio buffers to the custom source using appendBuffer().

  4. Stage — Wrap in LocalStageStream and publish to the stage via your StageStrategy.

  5. Participants — Stage participants receive the processed audio in real time.

Android

Creating a Custom Audio Source

After you create a DeviceDiscovery session, create a custom audio-input source:

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) );

This method returns a CustomAudioSource, which accepts raw PCM audio data. The custom audio source must be configured with the same audio format that your audio-processing pipeline produces.

Supported Audio Formats

Parameter Options Description
Channels 1 (mono), 2 (stereo) Number of audio channels.
Sample rate RATE_16000, RATE_44100, RATE_48000 Audio sample rate in Hz. 48kHz recommended for high quality.
Format INT16, FLOAT32 Audio sample format. INT16 is 16-bit fixed-point PCM, FLOAT32 is 32-bit floating-point PCM. Both interleaved and planar formats are available.

Submitting Audio Data

To submit audio data to the custom source, use the appendBuffer() method:

// 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 sourceint 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();

Important considerations:

  • Audio data must be in the format specified when creating the custom source.

  • Timestamps should be monotonically increasing and provided by your audio source for smooth audio playback.

  • Submit audio regularly to avoid gaps in the stream.

  • The method returns the number of samples processed (0 indicates failure).

Publishing to a Stage

Wrap the CustomAudioSource in an AudioLocalStageStream and return it from your 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);

Complete Example: Audio Processing Integration

Here’s a complete example showing integration with an audio-processing SDK:

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

Best Practices

Audio Format Consistency

Ensure the audio format you submit matches the format specified when creating the custom source:

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

Buffer Management

Use direct ByteBuffers and reuse them to minimize garbage collection:

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

Timing and Synchronization

You must use timestamps provided by your audio source for smooth audio playback. If your audio source does not provide its own timestamp, create your own epoch timestamp and manually calculate the duration between each sample using the number of frames and frame size.

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

Error Handling

Always check the return value of 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. }