IVS Broadcast SDK: カスタムオーディオソース | リアルタイムストリーミング
注: このガイドは、IVS リアルタイムストリーミング Android Broadcast SDK にのみ適用されます。iOS とウェブの SDK に関する情報は、今後公開される予定です。
カスタム音声入力ソースを使用すると、アプリケーションはデバイスの内蔵マイクに制限されるのではなく、ブロードキャスト SDK に独自の音声入力を提供できます。カスタムオーディオソースを使用すると、アプリケーションは処理された音声へのエフェクトの適用とストリーミング、複数のオーディオストリームのミックス、サードパーティーのオーディオ処理ライブラリとの統合を行うことができます。
カスタムオーディオ入力ソースを使用する場合、ブロードキャスト SDK ではマイクが直接管理されなくなります。代わりに、アプリケーションがオーディオデータをキャプチャ、処理し、カスタムソースに送信します。
カスタムオーディオソースのワークフローは、以下のステップに従います。
-
オーディオ入力 — 指定されたオーディオ形式 (サンプルレート、チャネル、形式) でカスタムオーディオソースを作成します。
-
処理 — オーディオ処理パイプラインからオーディオデータをキャプチャまたは生成します。
-
カスタムオーディオソース —
appendBuffer()を使用してカスタムソースにオーディオバッファを送信します。 -
ステージ —
LocalStageStreamにラップしてStageStrategy経由でステージに公開します。 -
参加者 — ステージ参加者は、処理された音声をリアルタイムで受け取ります。
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 は失敗を示します)。
ステージへの発行
CustomAudioSource を AudioLocalStageStream でラップし、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. }