IVS Broadcast SDK: 사용자 지정 오디오 소스 | 실시간 스트리밍
참고: 이 가이드는 IVS 실시간 스트리밍 Android Broadcast SDK에만 적용됩니다. iOS 및 웹 SDK에 대한 정보는 향후 게시될 예정입니다.
사용자 지정 오디오 입력 소스를 사용하면 애플리케이션이 디바이스의 내장 마이크로 제한되지 않고 Broadcast SDK에 자체 오디오 입력을 제공할 수 있습니다. 사용자 지정 오디오 소스를 사용하면 애플리케이션이 처리된 오디오를 효과와 함께 스트리밍하거나, 여러 오디오 스트림을 혼합하거나, 타사 오디오 처리 라이브러리와 통합할 수 있습니다.
사용자 지정 오디오 입력 소스를 사용하는 경우 Broadcast 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입니다. 인터리브 형식과 평면 형식을 모두 사용할 수 있습니다. |
오디오 데이터 제출
오디오 데이터를 사용자 지정 소스에 제출하려면 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은 실패를 나타냄).
스테이지에 게시
AudioLocalStageStream에서 CustomAudioSource를 래핑하고 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. }