

# IVS Broadcast SDK: カスタムオーディオソース \| リアルタイムストリーミング
<a name="broadcast-custom-audio-sources"></a>

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

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

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

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

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

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

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

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

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

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

### カスタムオーディオソースの作成
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

`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` を返します。カスタムオーディオソースは、オーディオ処理パイプラインが生成するのと同じオーディオ形式で設定する必要があります。

#### サポートされるオーディオ形式
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


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

### オーディオデータの送信
<a name="custom-audio-sources-android-submitting-audio-data"></a>

オーディオデータをカスタムソースに送信するには、`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 は失敗を示します）。

### ステージへの発行
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

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

### 完全な例: オーディオ処理の統合
<a name="custom-audio-sources-android-complete-example"></a>

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

### ベストプラクティス
<a name="custom-audio-sources-android-best-practices"></a>

#### オーディオ形式の整合性
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

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

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

#### バッファ管理
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

ガベージコレクションを最小限に抑えるために `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(); 
}
```

#### タイミングと同期
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

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

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

#### エラー処理
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

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