

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# SDK Siaran IVS: Sumber Audio Kustom \| Streaming Waktu Nyata
<a name="broadcast-custom-audio-sources"></a>

**Catatan:** Panduan ini hanya berlaku untuk SDK siaran Android streaming real-time IVS. Informasi untuk iOS dan web SDKs akan dipublikasikan di masa depan.

Sumber input audio khusus memungkinkan aplikasi untuk menyediakan input audio sendiri ke SDK siaran, alih-alih terbatas pada mikrofon bawaan perangkat. Sumber audio khusus memungkinkan aplikasi untuk mengalirkan audio yang diproses dengan efek, mencampur beberapa aliran audio, atau mengintegrasikan dengan pustaka pemrosesan audio pihak ketiga.

Saat Anda menggunakan sumber input audio khusus, SDK siaran tidak lagi bertanggung jawab untuk mengelola mikrofon secara langsung. Sebagai gantinya, aplikasi Anda bertanggung jawab untuk menangkap, memproses, dan mengirimkan data audio ke sumber khusus.

 custom-audio-sourceAlur kerja mengikuti langkah-langkah ini:

1. Input audio - Buat sumber audio khusus dengan format audio tertentu (laju sampel, saluran, format). 

1. Pemrosesan Anda — Tangkap atau hasilkan data audio dari pipeline pemrosesan audio Anda.

1. Sumber audio khusus - Kirim buffer audio ke sumber khusus menggunakan`appendBuffer()`.

1. Panggung — Bungkus `LocalStageStream` dan publikasikan ke panggung melalui Anda`StageStrategy`. 

1. Peserta — Peserta panggung menerima audio yang diproses secara real time.

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

### Membuat Sumber Audio Kustom
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Setelah Anda membuat `DeviceDiscovery` sesi, buat sumber input audio kustom:

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

Metode ini mengembalikan`CustomAudioSource`, yang menerima data audio PCM mentah. Sumber audio kustom harus dikonfigurasi dengan format audio yang sama dengan yang dihasilkan pipeline pemrosesan audio Anda.

#### Format Audio yang Didukung
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parameter | Opsi | Deskripsi | 
| --- | --- | --- | 
| Saluran | 1 (mono), 2 (stereo) | Jumlah saluran audio. | 
| Tingkat sampel | RATE\_16000, RATE\_44100, RATE\_48000 | Kecepatan sampel audio dalam Hz. 48kHz direkomendasikan untuk kualitas tinggi. | 
| Format | INT16, FLOAT32 | Format sampel audio. INT16 adalah PCM titik tetap 16-bit, adalah PCM floating-point FLOAT32 32-bit. Format interleaved dan planar tersedia. | 

### Mengirimkan Data Audio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Untuk mengirimkan data audio ke sumber kustom, gunakan `appendBuffer()` metode ini:

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

**Pertimbangan penting:**
+ Data audio harus dalam format yang ditentukan saat membuat sumber khusus.
+ Stempel waktu harus ditingkatkan secara monoton dan disediakan oleh sumber audio Anda untuk pemutaran audio yang lancar.
+ Kirim audio secara teratur untuk menghindari celah dalam aliran.
+ Metode mengembalikan jumlah sampel yang diproses (0 menunjukkan kegagalan). 

### Penerbitan ke Panggung
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Bungkus `CustomAudioSource` dalam `AudioLocalStageStream` dan kembalikan dari`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);
```

### Contoh Lengkap: Integrasi Pemrosesan Audio
<a name="custom-audio-sources-android-complete-example"></a>

Berikut adalah contoh lengkap yang menunjukkan integrasi dengan SDK pemrosesan 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(); 
      } 
   } 
}
```

### Praktik Terbaik
<a name="custom-audio-sources-android-best-practices"></a>

#### Konsistensi Format Audio
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Pastikan format audio yang Anda kirimkan cocok dengan format yang ditentukan saat membuat sumber kustom:

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

#### Manajemen Buffer
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

Gunakan langsung `ByteBuffers` dan gunakan kembali untuk meminimalkan pengumpulan sampah: 

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

#### Pengaturan Waktu dan Sinkronisasi
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Anda harus menggunakan stempel waktu yang disediakan oleh sumber audio Anda untuk pemutaran audio yang lancar. Jika sumber audio Anda tidak menyediakan stempel waktu sendiri, buat stempel waktu epoch Anda sendiri dan hitung durasi antara setiap sampel secara manual menggunakan jumlah bingkai dan ukuran bingkai. 

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

#### Penanganan Kesalahan
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Selalu periksa nilai pengembalian`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. 
}
```