본문으로 건너뛰기
Version: 6.1

커스텀 오디오 디바이스

기본적으로 PlanetKit은 통화 시작 시 마이크나 스피커 장치를 자동으로 감지하여 제어하며, 마이크에서 캡처한 오디오를 통화에서 사용하여 상대방에게 전달하고, 수신한 오디오 데이터를 스피커로 재생합니다. 하지만 애플리케이션의 요구사항에 따라 이 구조를 변경해야 할 필요가 있을 수 있습니다. 예를 들어, 사용자의 오디오 대신 파일의 오디오를 전송하거나, 수신한 오디오 데이터를 스피커가 아닌 파일로 저장하는 경우입니다.

이러한 요구를 충족할 수 있도록 PlanetKit은 원하는 오디오 데이터를 직접 공급할 수 있는 커스텀 오디오 소스(custom audio source) 기능과 수신한 오디오 데이터를 직접 처리할 수 있는 커스텀 오디오 싱크(custom audio sink) 기능을 제공합니다. 이 기능을 통해 개발자가 원하는 오디오 데이터를 직접 PlanetKit 모듈에 제공하여 상대방에게 전달하거나, 상대방으로부터 수신한 오디오 데이터를 직접 제어할 수 있습니다.

Note

커스텀 오디오 소스 사용 시 PlanetKit은 마이크 장치를 제어하지 않으며, 커스텀 오디오 싱크 사용 시에는 스피커 장치를 제어하지 않습니다.

지원 통화 유형최소 SDK 버전
1대1 통화, 그룹 통화(컨퍼런스)PlanetKit 6.0

활용 사례

커스텀 오디오 디바이스의 주요 활용 사례는 다음과 같습니다.

커스텀 오디오 소스

  • 음성 파일을 오디오 소스로 사용
  • 외부 오디오 소스(라이브 방송 중계나 웹 스트리밍) 연동
  • TTS(text-to-speech) 음성을 오디오 소스로 사용
  • 마이크 장치를 PlanetKit에서 제어하지 않고 애플리케이션에서 직접 제어

커스텀 오디오 싱크

  • 스피커 장치를 활성화하지 않고 통화 오디오를 파일로 녹음
  • 외부 장치로의 오디오 라우팅
  • 네트워크 오디오 스트리밍
  • 스피커 장치를 PlanetKit에서 제어하지 않고 애플리케이션에서 직접 제어

API 개요

PlanetKit에서는 커스텀 오디오 디바이스를 구현하기 위한 클래스를 다음과 같이 제공합니다.

  • 커스텀 오디오 소스: PlanetKit::CustomMic
  • 커스텀 오디오 싱크: PlanetKit::CustomSpeaker

커스텀 오디오 디바이스를 구현하고 PlanetKit::AudioManager를 통해 아래와 같이 설정하거나 해제할 수 있습니다.

// Set custom audio source/sink
PlanetKit::AudioManager::ChangeMic(CustomMicPtr pCustomMic)
PlanetKit::AudioManager::ChangeSpeaker(CustomSpeakerPtr pCustomSpeaker)

// Reset to default devices
PlanetKit::AudioManager::ChangeMic(AudioDeviceInfoPtr pInfo = nullptr)
PlanetKit::AudioManager::ChangeSpeaker(AutioDeviceInfoPtr pInfo = nullptr)

구현 절차

예제 1: 커스텀 오디오 소스 - 오디오 파일 재생

커스텀 오디오 소스 기능을 구현하는 절차는 다음과 같습니다.

커스텀 오디오 소스 클래스 구현하기

PlanetKit::CustomMic를 상속받는 클래스를 만들고 메서드를 구현하세요.

#include <thread>
#include <atomic>
#include <vector>

#include "PlanetKitCustomMic.h"
#include "PlanetKitManager.h"

class AudioFileMicrophone : public PlanetKit::CustomMic {
public:
AudioFileMicrophone() {
// Allocate buffer for 48kHz 20ms = 960 samples
m_audioBuffer.resize(960);
}

virtual ~AudioFileMicrophone() = default;

virtual bool SetVolumeLevel(float fVolume) override { return true; };
virtual float GetVolumeLevel() override { return 1.0f; };
virtual float GetPeakValue() override { return 1.0f; };
virtual bool IsRunning() override { return m_isRunning; }

virtual bool RegisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) override { return true; }
virtual bool DeregisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) override { return true; }

virtual PlanetKit::AudioDeviceInfoPtr GetDeviceInfo() override { return nullptr; }

bool Start() {
if (m_isRunning.load()) {
return true; // Already running
}

m_isRunning = true;
m_audioThread = std::thread(&CustomMic::AudioThreadFunc, this);

return true;
}

bool Stop() {
if (!m_isRunning.load()) {
return true; // Already stopped
}

m_isRunning = false;

if (m_audioThread.joinable()) {
m_audioThread.join();
}

return true;
}

private:
void AudioThreadFunc() {
while (m_isRunning.load()) {
// Prepare audio data structure
PlanetKit::SAudioData audioData;
ZeroMemory(&audioData, sizeof(PlanetKit::SAudioData));

audioData.unAudioDataSamplingRate = 48000; // 48kHz

// Calculate samples needed for the requested duration
unsigned int samplesFor20ms = audioData.unAudioDataSamplingRate * 20 / 1000;

audioData.unAudioDataSampleCount = samplesFor20ms;
audioData.eAudioDataSampleFormat = PlanetKit::EAudioDataSampleType::PLNK_AUDIO_DATA_SAMPLE_TYPE_FLOAT_32;
audioData.unBufferSize = (unsigned int)(samplesFor20ms * sizeof(float));
audioData.ucBuffer = reinterpret_cast<unsigned char*>(m_audioBuffer.data());

// Read audio data from file using audioData.ucBuffer
...

// Send audio data to PlanetKit
PutAudioData(audioData);

// Sleep for 20ms (50fps)
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}

private:
std::thread m_audioThread;
std::atomic<bool> m_isRunning;
std::vector<float> m_audioBuffer;
};

커스텀 오디오 소스 사용하기

커스텀 오디오 소스를 사용하려면 구현한 커스텀 오디오 소스 객체를 인자로 PlanetKit::AudioManagerChangeMic()를 호출하세요.

// Create CustomMic instance by using AudioFileMicrophone class
PlanetKit::CustomMicPtr customMic = PlanetKit::MakeAutoPtr<AudioFileMicrophone>();

// Set CustomMic instance to PlanetKit
auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();
audioManager->ChangeMic(customMic);

customMic.as<AudioFileMicrophone>()->Start();

커스텀 오디오 소스를 마이크로 되돌려 놓기

커스텀 오디오 소스를 마이크로 되돌려 놓으려면 사용하고 싶은 마이크 정보를 획득해 이를 인자로 PlanetKit::AudioManagerChangeMic()를 호출하세요.

auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();

// Get system default mic
auto defaultMic = audioManager->GetDefaultMicInfo();

// Change mic
audioManager->ChangeMic(defaultMic);

예제 2: 커스텀 오디오 싱크 - 오디오 녹음

커스텀 오디오 싱크 기능을 구현하는 절차는 다음과 같습니다.

커스텀 오디오 싱크 클래스 구현하기

PlanetKit::CustomSpeaker를 상속받는 클래스를 만들고 메서드를 구현하세요.

#include "PlanetKitCustomSpeaker.h"
#include "PlanetKitManager.h"

#include <string>
#include <fstream>
#include <atomic>
#include <thread>

class AudioFileRecorder : public PlanetKit::CustomSpeaker {
public:
AudioFileRecorder();
virtual ~AudioFileRecorder() = default;

virtual bool IsRunning() override { return m_isRecording.load(); }
virtual bool SetVolumeLevel(float fVolume) override { return true; }
virtual float GetVolumeLevel() override { return 1.0f; }
virtual float GetPeakValue() override { return 1.0f; }
virtual bool RegisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) { return true; }
virtual bool DeregisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) { return true; }
virtual PlanetKit::AudioDeviceInfoPtr GetDeviceInfo() override { return nullptr; }
virtual bool PlayFile(const PlanetKit::WString& strFilePath, unsigned int unLoop) { return true; }
virtual bool StopPlay() override { return true; }
virtual bool IsCustomSpeaker() override { return true; }

bool Start(const std::string& filePath) {
if (m_isRecording.load()) {
return false;
}

// Open file
m_recordingFile.open(filePath, std::ios::binary);
if (!m_recordingFile.is_open()) {
return false;
}

m_hAudioThread = std::thread(std::bind(&AudioFileRecorder::PullAudioThread, this));

return true;
}

void Stop() {
if (!m_isRecording.load()) {
return;
}

m_isRecording.store(false);

if (m_hAudioThread.joinable()) {
m_hAudioThread.join();
}

if (m_recordingFile.is_open()) {
m_recordingFile.close();
}
}

private:
void PullAudioThread() {
m_isRecording.store(true);
while(m_isRunning) {
// 100Hz update rate (10ms intervals) - matches sample buffer size
Sleep(10);

if(this->PullAudioData(m_audioData) == true) {
// Write audioData to file using m_audioData.ucBuffer
}
}
}


private:
std::atomic<bool> m_isRecording{false};
std::thread m_hAudioThread;

std::ofstream m_recordingFile;
PlanetKit::SAudioData m_audioData;
}

커스텀 오디오 싱크 사용하기

커스텀 오디오 싱크를 사용하려면 구현한 커스텀 오디오 싱크 객체를 인자로 PlanetKit::AudioManagerChangeSpeaker()를 호출하세요.

// Create CustomSpeaker instance by using AudioFileRecorder class
PlanetKit::CustomSpeakerPtr customSpeaker = PlanetKit::MakeAutoPtr<AudioFileRecorder>();

// Set CustomSpeaker instance to PlanetKit
auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();
audioManager->ChangeSpeaker(customSpeaker);

customSpeaker.as<AudioFileRecorder>()->Start("audio.wav");

커스텀 오디오 싱크를 스피커로 되돌려 놓기

커스텀 오디오 싱크를 스피커로 되돌려 놓으려면 사용하고 싶은 스피커 정보를 획득해 이를 인자로 PlanetKit::AudioManagerChangeSpeaker()를 호출하세요.

auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();

// Get system default speaker
auto defaultSpeaker = audioManager->GetDefaultSpeakerInfo();

// Change speaker
audioManager->ChangeSpeaker(defaultSpeaker);

주요 고려사항

커스텀 오디오 디바이스 기능을 사용할 때 고려해야 할 사항은 다음과 같습니다.

오디오 포맷

구분16-bit PCM32-bit IEEE
샘플링 레이트48000 Hz48000 Hz
채널1 (Mono)1 (Mono)
샘플 포맷PCMIEEE Float

성능 최적화

  • 성능을 위해 메모리 할당 및 해제를 최소화해야 합니다.
  • 오디오 처리는 반드시 별도 스레드에서 수행하세요.

커스텀 오디오 디바이스 사용 시 AEC 설정

AEC(acoustic echo cancellation)는 스피커에서 출력된 소리가 마이크로 다시 입력되어 발생하는 에코를 제거하는 기술입니다. PlanetKit에서 제공하는 AEC 관련 기능으로는 VQE 제어의 AEC애플리케이션 AEC 레퍼런스가 있습니다. 일반적으로는 AEC 관련하여 별도의 설정이 필요하지 않지만, 커스텀 오디오 디바이스를 이용하는 경우 상황에 따라 AEC 관련 설정을 직접 수행해야 합니다.

AEC 관련 설정이 필요한 조건

애플리케이션의 구현이 아래의 조건에 해당하는 경우에는 AEC 관련 설정을 직접 수행해야 합니다.

  • Case 1: VQE 제어의 AEC 설정이 필요한 경우
    • 조건
      • 아래의 조건 중 하나라도 해당하는 경우
        • 커스텀 오디오 소스에서 실제 마이크 장치가 아닌 다른 입력(예: wav 파일, HTTP URL 등)을 사용하는 경우
        • 커스텀 오디오 싱크에서 실제 스피커 장치가 아닌 다른 출력(예: 파일 레코딩)을 사용하는 경우
    • 필요 설정
      • 커스텀 오디오 디바이스 사용 시작 후 SetAcousticEchoCanceller(PLNK_ACOUSTIC_ECHO_CANCELLER_DISABLED) 호출
      • 커스텀 오디오 디바이스 사용 종료 후 SetAcousticEchoCanceller(PLNK_ACOUSTIC_ECHO_CANCELLER_INTENSITY_RECOMMENDED) 호출
  • Case 2: 애플리케이션 AEC 레퍼런스 설정이 필요한 경우
    • 조건
      • PlanetKit에서 전달된 스피커 데이터를 커스텀 오디오 싱크에서 변조하여 출력하는 경우
      • 단, 오디오 입력이 실제 마이크 장치일 경우에만 해당(입력이 wav 파일 등 실제 에코가 입력될 수 있는 장치가 아닐 경우엔 해당 없음)
    • 필요 설정
      • 커스텀 오디오 싱크 사용 시작 후 StartUserAcousticEchoCancellerReference() 호출
      • PutUserAcousticEchoCancellerReference()로 스피커에 출력하고자 하는 음원을 제공
      • 커스텀 오디오 싱크 사용 종료 후 StopUserAcousticEchoCancellerReference() 호출

Case 1: VQE 제어의 AEC 설정이 필요한 경우

커스텀 오디오 소스 또는 커스텀 오디오 싱크를 이용해 실제 마이크와 스피커가 아닌 다른 입력 또는 출력을 사용한다면, 통화 세션 클래스인 PlanetKit::PlanetKitCallPlanetKit::PlanetKitConference에서 제공하는 VQE 기능 중 하나인 AEC 사용을 중지해 통화 품질을 향상시킬 수 있습니다. 통화 세션 클래스에서 GetSendVoiceProcessor()를 이용해 SendVoiceProcessor 클래스 인스턴스를 획득하고 SetAcousticEchoCanceller()를 호출해 AEC 모드를 설정할 수 있습니다.

커스텀 오디오 소스 클래스 구현하기

커스텀 오디오 소스를 이용해 오디오 파일을 입력으로 사용하기 위한 클래스를 구현합니다.

#include <thread>
#include <atomic>
#include <vector>

#include "PlanetKitCustomMic.h"
#include "PlanetKitManager.h"

class AudioFileMicrophone : public PlanetKit::CustomMic {
public:
AudioFileMicrophone() {
// Allocate buffer for 48kHz 20ms = 960 samples
m_audioBuffer.resize(960);
}

virtual ~AudioFileMicrophone() = default;

virtual bool SetVolumeLevel(float fVolume) override { return true; };
virtual float GetVolumeLevel() override { return 1.0f; };
virtual float GetPeakValue() override { return 1.0f; };
virtual bool IsRunning() override { return m_isRunning; }

virtual bool RegisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) override { return true; }
virtual bool DeregisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) override { return true; }

virtual PlanetKit::AudioDeviceInfoPtr GetDeviceInfo() override { return nullptr; }

bool Start() {
if (m_isRunning.load()) {
return true; // Already running
}

m_isRunning = true;
m_audioThread = std::thread(&CustomMic::AudioThreadFunc, this);

return true;
}

bool Stop() {
if (!m_isRunning.load()) {
return true; // Already stopped
}

m_isRunning = false;

if (m_audioThread.joinable()) {
m_audioThread.join();
}

return true;
}

private:
void AudioThreadFunc() {
while (m_isRunning.load()) {
// Prepare audio data structure
PlanetKit::SAudioData audioData;
ZeroMemory(&audioData, sizeof(PlanetKit::SAudioData));

audioData.unAudioDataSamplingRate = 48000; // 48kHz

// Calculate samples needed for the requested duration
unsigned int samplesFor20ms = audioData.unAudioDataSamplingRate * 20 / 1000;

audioData.unAudioDataSampleCount = samplesFor20ms;
audioData.eAudioDataSampleFormat = PlanetKit::EAudioDataSampleType::PLNK_AUDIO_DATA_SAMPLE_TYPE_FLOAT_32;
audioData.unBufferSize = (unsigned int)(samplesFor20ms * sizeof(float));
audioData.ucBuffer = reinterpret_cast<unsigned char*>(m_audioBuffer.data());

// Read audio data from file using audioData.ucBuffer
...

// Send audio data to PlanetKit
PutAudioData(audioData);

// Sleep for 20ms (50fps)
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}

private:
std::thread m_audioThread;
std::atomic<bool> m_isRunning;
std::vector<float> m_audioBuffer;
};

커스텀 오디오 소스 사용하기

커스텀 오디오 소스를 사용하려면 구현한 커스텀 오디오 소스 객체를 인자로 PlanetKit::AudioManagerChangeMic()를 호출하고, EPlanetKitAcousticEchoCanceller::PLNK_ACOUSTIC_ECHO_CANCELLER_DISABLED를 인자로 SendVoiceProcessor 클래스의 SetAcousticEchoCanceller()를 호출해 AEC를 사용하지 않도록 설정합니다.

// Prepare your call session instance
PlanetKit::PlanetKitCallPtr call;

// Create CustomMic instance by using AudioFileMicrophone class
PlanetKit::CustomMicPtr customMic = PlanetKit::MakeAutoPtr<AudioFileMicrophone>();

// Set CustomMic instance to PlanetKit
auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();
audioManager->ChangeMic(customMic);

// Get instance of SendVoiceProcessor class
auto sendVoiceProcessor = call->GetSendVoiceProcessor();

// Set AEC to disabled mode
sendVoiceProcessor->SetAcousticEchoCanceller(PlanetKit::EPlanetKitAcousticEchoCanceller::PLNK_ACOUSTIC_ECHO_CANCELLER_DISABLED);

커스텀 오디오 소스를 마이크로 되돌려 놓기

커스텀 오디오 소스를 마이크로 되돌려 놓으려면 사용하고자 하는 마이크 정보를 획득해 이를 인자로 PlanetKit::AudioManagerChangeMic()를 호출하고, EPlanetKitAcousticEchoCanceller::PLNK_ACOUSTIC_ECHO_CANCELLER_INTENSITY_RECOMMENDED를 인자로 SendVoiceProcessor 클래스의 SetAcousticEchoCanceller()를 호출해 AEC를 사용하도록 설정합니다.

// Prepare your call session instance
PlanetKit::PlanetKitCallPtr call;

auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();

// Get system default mic
auto defaultMic = audioManager->GetDefaultMicInfo();

// Change mic
audioManager->ChangeMic(defaultMic);

// Get instance of SendVoiceProcessor class
auto sendVoiceProcessor = call->GetSendVoiceProcessor();

// Set AEC to recommended mode
sendVoiceProcessor->SetAcousticEchoCanceller(PlanetKit::EPlanetKitAcousticEchoCanceller::PLNK_ACOUSTIC_ECHO_CANCELLER_INTENSITY_RECOMMENDED);

Case 2: 애플리케이션 AEC 레퍼런스 설정이 필요한 경우

통화 세션 클래스인 PlanetKit::PlanetKitCallPlanetKit::PlanetKitConference에서 제공하는 StartUserAcousticEchoCancellerReference()StopUserAcousticEchoCancellerReference()를 이용해 AEC 레퍼런스 데이터 사용 여부를 설정할 수 있습니다. AEC 레퍼런스 데이터 사용 설정 후 통화 세션 클래스의 PutUserAcousticEchoCancellerReference()를 이용해 변경된 오디오 데이터를 제공하면 됩니다.

커스텀 오디오 싱크 클래스 구현하기

커스텀 오디오 싱크를 이용해 오디오 출력 데이터를 변경하기 위한 클래스를 구현합니다.

  • 통화에 연결된 이후, 수신된 오디오 데이터를 획득하기 위해서 주기적으로 PullAudioData()를 호출하세요.
  • 오디오 데이터를 가공하고 이를 인자로 PutUserAcousticEchoCancellerReference()를 호출하세요.
#include "PlanetKitCustomSpeaker.h"
#include "PlanetKitManager.h"

#include <atomic>
#include <thread>

class AudioSinkModifier : public PlanetKit::CustomSpeaker {
public:
AudioSinkModifier(PlanetKit::PlanetKitCallPtr call) {
m_call = call;

audioData.unAudioDataSamplingRate = 48000; // 48kHz default
audioData.unAudioDataSampleCount = 480; // 10ms at 48kHz
audioData.eAudioDataSampleFormat = PlanetKit::PLNK_AUDIO_DATA_SAMPLE_TYPE_SHORT16;
audioData.buffer = new unsigned char[audioData.unAudioDataSampleCount * sizeof(unsigned char)];
audioData.unBufferSize = audioData.unAudioDataSampleCount * sizeof(unsigned char);
}

virtual ~AudioSinkModifier() = default;

virtual bool IsRunning() override { return true; }
virtual bool SetVolumeLevel(float fVolume) override { return true; }
virtual float GetVolumeLevel() override { return 1.0f; }
virtual float GetPeakValue() override { return 1.0f; }
virtual bool RegisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) { return true; }
virtual bool DeregisterVolumeLevelChangedEvent(PlanetKit::AudioVolumeLevelChangedEventPtr) { return true; }
virtual PlanetKit::AudioDeviceInfoPtr GetDeviceInfo() override { return nullptr; }
virtual bool PlayFile(const PlanetKit::WString& strFilePath, unsigned int unLoop) { return true; }
virtual bool StopPlay() override { return true; }
virtual bool IsCustomSpeaker() override { return true; }

void Start() {
if (m_isRecording.load()) {
return false;
}

m_hAudioThread = std::thread(std::bind(& AudioSinkModifier::PullAudioThread, this));

return true;
}

void Stop() {
if (!m_isRecording.load()) {
return;
}

m_isRecording.store(false);

if (m_hAudioThread.joinable()) {
m_hAudioThread.join();
}
}

private:
void PullAudioThread() {
m_isRecording.store(true);
while(m_isRunning) {
// 100Hz update rate (10ms intervals) - matches sample buffer size
Sleep(10);

if(this->PullAudioData(m_audioData) == true) {
// Modify audioData and Play to physical speaker
PlanetKit::SAudioData modifyAudioData;

m_call->PutUserAcousticEchoCancellerReference(modifyAudioData);
}
}
}

private:
PlanetKit::PlanetKitCallPtr m_call;
std::atomic<bool> m_isRecording{false};
std::thread m_hAudioThread;

PlanetKit::SAudioData m_audioData;
}

커스텀 오디오 싱크 사용하기

커스텀 오디오 싱크를 사용하려면 구현한 커스텀 오디오 싱크 객체를 인자로 PlanetKit::AudioManagerChangeSpeaker()를 호출하고, 애플리케이션 AEC 레퍼런스 사용을 시작하도록 통화 세션 클래스의 StartUserAcousticEchoCancellerReference()를 호출하세요.

// Prepare your call session instance
PlanetKit::PlanetKitCallPtr call;

// Create CustomSpeaker instance by using AudioSinkModifier class
PlanetKit::CustomSpeakerPtr customSpeaker = PlanetKit::MakeAutoPtr<AudioSinkModifier>(call);

// Set CustomSpeaker instance to PlanetKit
auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();
audioManager->ChangeSpeaker(customSpeaker);

call->StartUserAcousticEchoCancellerReference();

커스텀 오디오 싱크를 스피커로 되돌려 놓기

커스텀 오디오 싱크를 스피커로 되돌려 놓으려면 사용하고자 하는 스피커 정보를 획득해 이를 인자로 PlanetKit::AudioManagerChangeSpeaker()를 호출하고, 애플리케이션 AEC 레퍼런스 사용을 중지하도록 통화 세션 클래스의 StopUserAcousticEchoCancellerReference()를 호출하세요.

// Prepare your call session instance
PlanetKit::PlanetKitCallPtr call;

auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();

// Get system default speaker
auto defaultSpeaker = audioManager->GetDefaultSpeakerInfo();

// Change speaker
audioManager->ChangeSpeaker(defaultSpeaker);

// Stop AEC reference
call->StopUserAcousticEchoCancellerReference();

관련 API