IVS Broadcast SDK: 사용자 지정 오디오 소스 | 실시간 스트리밍 - Amazon IVS

IVS Broadcast SDK: 사용자 지정 오디오 소스 | 실시간 스트리밍

참고: 이 가이드는 IVS 실시간 스트리밍 Android Broadcast SDK에만 적용됩니다. iOS 및 웹 SDK에 대한 정보는 향후 게시될 예정입니다.

사용자 지정 오디오 입력 소스를 사용하면 애플리케이션이 디바이스의 내장 마이크로 제한되지 않고 Broadcast SDK에 자체 오디오 입력을 제공할 수 있습니다. 사용자 지정 오디오 소스를 사용하면 애플리케이션이 처리된 오디오를 효과와 함께 스트리밍하거나, 여러 오디오 스트림을 혼합하거나, 타사 오디오 처리 라이브러리와 통합할 수 있습니다.

사용자 지정 오디오 입력 소스를 사용하는 경우 Broadcast 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입니다. 인터리브 형식과 평면 형식을 모두 사용할 수 있습니다.

오디오 데이터 제출

오디오 데이터를 사용자 지정 소스에 제출하려면 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. }