IVS Broadcast SDK: カスタムオーディオソース | リアルタイムストリーミング - Amazon IVS

IVS Broadcast SDK: カスタムオーディオソース | リアルタイムストリーミング

注: このガイドは、IVS リアルタイムストリーミング Android Broadcast SDK にのみ適用されます。iOS とウェブの SDK に関する情報は、今後公開される予定です。

カスタム音声入力ソースを使用すると、アプリケーションはデバイスの内蔵マイクに制限されるのではなく、ブロードキャスト SDK に独自の音声入力を提供できます。カスタムオーディオソースを使用すると、アプリケーションは処理された音声へのエフェクトの適用とストリーミング、複数のオーディオストリームのミックス、サードパーティーのオーディオ処理ライブラリとの統合を行うことができます。

カスタムオーディオ入力ソースを使用する場合、ブロードキャスト SDK ではマイクが直接管理されなくなります。代わりに、アプリケーションがオーディオデータをキャプチャ、処理し、カスタムソースに送信します。

カスタムオーディオソースのワークフローは、以下のステップに従います。

  1. オーディオ入力 — 指定されたオーディオ形式 (サンプルレート、チャネル、形式) でカスタムオーディオソースを作成します。

  2. 処理 — オーディオ処理パイプラインからオーディオデータをキャプチャまたは生成します。

  3. カスタムオーディオソース — appendBuffer() を使用してカスタムソースにオーディオバッファを送信します。

  4. ステージ — LocalStageStream にラップして StageStrategy 経由でステージに公開します。

  5. 参加者 — ステージ参加者は、処理された音声をリアルタイムで受け取ります。

Android

カスタムオーディオソースの作成

DeviceDiscovery セッションを作成した後、カスタム音声入力ソースを作成します。

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

このメソッドは、未加工の PCM オーディオデータを受け入れる CustomAudioSource を返します。カスタムオーディオソースは、オーディオ処理パイプラインが生成するのと同じオーディオ形式で設定する必要があります。

サポートされるオーディオ形式

パラメータ オプション 説明
チャンネル 1 (モノラル)、2 (ステレオ) オーディオチャネルの数。
サンプルレート RATE_16000、RATE_44100、RATE_48000 オーディオサンプルレート (Hz)。高品質には 48kHz が推奨されます。
形式 INT16、FLOAT32 オーディオサンプル形式。INT16 は 16 ビットの固定ポイント PCM です。FLOAT32 は 32 ビットの浮動小数点 PCM です。インターリーブ形式と Planar 平面形式の両方を使用できます。

オーディオデータの送信

オーディオデータをカスタムソースに送信するには、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();

重要な考慮事項:

  • オーディオデータは、カスタムソースの作成時に指定された形式である必要があります。

  • スムーズなオーディオ再生のために、タイムスタンプは一定間隔で増加し、オーディオソースによって提供される必要があります。

  • ストリームのギャップを避けるため、オーディオを定期的に送信します。

  • メソッドは、処理されたサンプルの数を返します (0 は失敗を示します)。

ステージへの発行

CustomAudioSourceAudioLocalStageStream でラップし、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);

完全な例: オーディオ処理の統合

オーディオ処理 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(); } } }

ベストプラクティス

オーディオ形式の整合性

送信するオーディオ形式が、カスタムソースの作成時に指定された形式と一致していることを確認します。

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

バッファ管理

ガベージコレクションを最小限に抑えるために ByteBuffers を直接使用して再利用します。

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

タイミングと同期

オーディオをスムーズに再生するには、オーディオソースが提供するタイムスタンプを使用する必要があります。オーディオソースに独自のタイムスタンプがない場合は、独自のエポックタイムスタンプを作成し、フレーム数とフレームサイズを使用して各サンプル間の期間を手動で計算します。

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

エラー処理

必ず 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. }