カスタムオーディオデバイス
デフォルトでは、PlanetKitは通話開始時にマイクやスピーカーデバイスを自動的に検出して制御し、マイクでキャプチャーしたオーディオを通話で使用して相手に転送し、受信したオーディオデータをスピーカーで再生します。ただし、アプリケーションの要求事項によっては、この構造を変更する必要があります。たとえば、ユーザーのオーディオの代わりにファイルのオーディオを転送したり、受信したオーディオデータをスピーカー以外のファイルとして保存したりする場合です。
このような要求を満たすために、PlanetKitは希望するオーディオデータを直接供給できる**カスタムオーディオソース(custom audio source)機能と、受信したオーディオデータを直接処理できるカスタムオーディオシンク(custom audio sink)**機能を提供します。この機能により、開発者が希望するオーディオデータをPlanetKitモジュールに提供して相手に転送したり、相手から受信したオーディオデータを直接制御したりできます。
PlanetKitは、カスタムオーディオソースを使用時にはマイクデバイスを制御せず、カスタムオーディオシンクを使用時にはスピーカーデバイスを制御しません。
| 通話タイプ | SDKの最低バージョン |
|---|---|
| 1対1通話、グループ通話(カンファレンス) | PlanetKit 6.0 |
活用事例
カスタムオーディオデバイスの主な活用例は次のとおりです。
カスタムオーディオソース
- 音声ファイルをオーディオソースとして使用
- 外部オーディオソース(ライブ配信中継やWebストリーミング)連動
- 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::AudioManagerのChangeMic()を呼び出します。
// 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::AudioManagerのChangeMic()を呼び出します。
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::AudioManagerのChangeSpeaker()を呼び出します。
// 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::AudioManagerのChangeSpeaker()を呼び出します。
auto audioManager = PlanetKit::PlanetKitManager::GetInstance()->GetAudioManager();
// Get system default speaker
auto defaultSpeaker = audioManager->GetDefaultSpeakerInfo();
// Change speaker
audioManager->ChangeSpeaker(defaultSpeaker);
主な考慮事項
カスタムオーディオデバイス機能を使用する際に考慮すべき事項は次のとおりです。
オーディオフォーマット
| 区分 | 16-bit PCM | 32-bit IEEE |
|---|---|---|
| サンプルレート | 48000 Hz | 48000 Hz |
| チャンネル | 1 (Mono) | 1 (Mono) |
| サンプルフォーマット | PCM | IEEE 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::PlanetKitCallおよびPlanetKit::PlanetKitConferenceで提供されるVQE機能の1つである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::AudioManagerのChangeMic()を呼び出し、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::AudioManagerのChangeMic()を呼び出し、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::PlanetKitCallおよびPlanetKit::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::AudioManagerのChangeSpeaker()を呼び出し、アプリケーション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::AudioManagerのChangeSpeaker()を呼び出し、アプリケーション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();