本文にスキップする
Version: 7.0

カスタムオーディオデバイス

デフォルトでは、PlanetKitは通話開始時にマイクやスピーカーデバイスを自動的に検出して制御し、マイクでキャプチャーしたオーディオを通話で使用して相手に転送し、受信したオーディオデータをスピーカーで再生します。ただし、アプリケーションの要求事項によっては、この構造を変更する必要があります。たとえば、ユーザーのオーディオの代わりにファイルのオーディオを転送したり、受信したオーディオデータをスピーカー以外のファイルとして保存したりする場合です。

このような要求を満たすために、PlanetKitは希望するオーディオデータを直接供給できるカスタムオーディオソース(custom audio source)機能と、受信したオーディオデータを直接処理できるカスタムオーディオシンク(custom audio sink)機能を提供します。この機能により、開発者が希望するオーディオデータをPlanetKitモジュールに提供して相手に転送したり、相手から受信したオーディオデータを直接制御したりできます。

Note

PlanetKitは、カスタムオーディオソースを使用時にはマイクデバイスを制御せず、カスタムオーディオシンクを使用時にはスピーカーデバイスを制御しません。

対応する通話タイプSDKの最低バージョン
1対1通話、グループ通話(カンファレンス)PlanetKit 6.0

活用事例

カスタムオーディオデバイスの主な活用例は次のとおりです。

カスタムオーディオソース

  • 音声ファイルをオーディオソースとして使用
  • 外部オーディオソース(ライブ配信中継やWebストリーミング)連動
  • 加工されたオーディオデータ、または複数のオーディオソースをミキシングしたデータをオーディオソースとして使用
  • マイクデバイスをPlanetKitで制御せずにアプリケーションから直接制御

カスタムオーディオシンク

  • スピーカーデバイスを有効にせずに通話オーディオをファイルとして録音
  • 外部デバイスへのオーディオルーティング
  • ネットワークオーディオストリーミング
  • スピーカーデバイスをPlanetKitで制御せずにアプリケーションから直接制御

実装手順

例1:カスタムオーディオソース - オーディオファイルの再生

カスタムオーディオソース機能を実装する手順は、次のとおりです。

カスタムオーディオソースクラスを実装する

PlanetKitCustomAudioSourceを拡張(extend)し、メソッドを実装します。

import com.linecorp.planetkit.audio.PlanetKitCustomAudioSource
import com.linecorp.planetkit.audio.AudioFrame
import com.linecorp.planetkit.audio.PlanetKitAudioSampleType
import com.linecorp.planetkit.audio.PlanetKitAudioSampleRate
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.Timer
import kotlin.math.PI
import kotlin.math.sin
import kotlin.concurrent.fixedRateTimer

// Example implementation of a custom audio source
class MyCustomAudioSource : PlanetKitCustomAudioSource() {

// Audio configuration settings
private val sampleType = PlanetKitAudioSampleType.SIGNED_SHORT_16
private val sampleRate = PlanetKitAudioSampleRate.SAMPLE_RATE_48K

// Streaming state management
private var isStreaming = false
private var timer: Timer? = null

// Start audio streaming
fun start() {
if (!isStreaming) {
isStreaming = true

timer = fixedRateTimer(
name = "CustomAudioSourceTimer",
initialDelay = 0L,
period = 20L // 20ms cycle
) {
if (isStreaming) {
generateAndSendAudioFrames()
}
}
}
}

// Stop audio streaming
fun stop() {
isStreaming = false
timer?.cancel()
timer = null
}

// Main loop that generates and sends audio frames every 20ms
private fun generateAndSendAudioFrames() {
// Generate audio data (example: sine wave)
val audioData = generateSineWave()

// Create ByteBuffer
val buffer = ByteBuffer.allocate(audioData.size * 2).order(ByteOrder.LITTLE_ENDIAN)
for (sample in audioData) {
buffer.putShort(sample)
}

buffer.flip()

// Create and send AudioFrame
val audioFrame = createAudioFrame(buffer)
postFrameData(audioFrame)
audioFrame.release()
}

private fun generateSineWave(): ShortArray {
val samples = ShortArray(960) // 20ms at 48kHz = 960 samples
for (i in samples.indices) {
val time = i.toDouble() / sampleRate.sampleRate
val amplitude = (Short.MAX_VALUE * 0.3).toInt()
samples[i] = (amplitude * sin(2 * PI * 440 * time)).toInt().toShort()
}
return samples
}

private fun createAudioFrame(buffer: ByteBuffer): AudioFrame {
return object : AudioFrame {
override fun getBuffer(): ByteBuffer = buffer
override fun getSampleType(): PlanetKitAudioSampleType = sampleType
override fun getSize(): Long = buffer.remaining().toLong()
override fun getSamplingRate(): PlanetKitAudioSampleRate = sampleRate
override fun getTimestamp(): Long = System.nanoTime()
override fun release() {}
}
}
}

カスタムオーディオソースを使用して相手にオーディオを転送する

PlanetKitCustomAudioSourceオブジェクトを引数として、PlanetKitCallまたはPlanetKitConferencesetCustomAudioSource()を呼び出してカスタムオーディオソースを開始します。

カスタムオーディオソースが開始されると、カスタムオーディオソースから呼び出されるpostFrameData()メソッドを介してオーディオデータが送信されます。

val customAudioSource = MyCustomAudioSource()

// For 1-to-1 call
fun useCustomAudioSourceForCall(call: PlanetKitCall) {
call.setCustomAudioSource(customAudioSource)
// Execute the start logic you created.
customAudioSource.start()
}

// For group call
fun useCustomAudioSourceForConference(conference: PlanetKitConference) {
conference.setCustomAudioSource(customAudioSource)
// Execute the start logic you created.
customAudioSource.start()
}

カスタムオーディオソースをマイクに戻しておく

カスタムオーディオソースをマイクに戻すには、PlanetKitCallまたはPlanetKitConferenceclearCustomAudioSource()を呼び出してカスタムオーディオソースを停止します。

val customAudioSource = MyCustomAudioSource()

// For 1-to-1 call
fun useDefaultAudioSourceForCall(call: PlanetKitCall) {
call.clearCustomAudioSource()
// Execute the stop logic you created.
customAudioSource.stop()
}

// For group call
fun useDefaultAudioSourceForConference(conference: PlanetKitConference) {
conference.clearCustomAudioSource()
// Execute the stop logic you created.
customAudioSource.stop()
}

例2:カスタムオーディオシンク - オーディオ録音

カスタムオーディオシンク機能を実装する手順は、次のとおりです。

カスタムオーディオシンククラスを実装する

PlanetKitCustomAudioSinkを拡張(extend)し、メソッドを実装します。

/**
* Example implementation of PlanetKitCustomAudioSink for recording audio to WAV file.
*
* This example demonstrates how to:
* - Pull audio data from PlanetKit periodically
* - Write audio data to WAV file with proper header
* - Support reusable start/stop operations
*
* The recorded WAV file is saved to the app's external files directory.
*/
class MyCustomAudioRecorder(private val context: Context) : PlanetKitCustomAudioSink() {

companion object {
// Audio configuration
private const val CHANNEL_COUNT = 1 // PlanetKit only supports mono channel
private val SAMPLE_RATE = PlanetKitAudioSampleRate.SAMPLE_RATE_16K
private const val PULL_INTERVAL_MS = 20 // Pull audio data every 20ms
private val SAMPLE_TYPE = PlanetKitAudioSampleType.SIGNED_SHORT_16
// Calculate sample count per pull: 20ms * 16000Hz / 1000 = 320 samples
private val SAMPLE_COUNT = (PULL_INTERVAL_MS * SAMPLE_RATE.sampleRate) / 1000
}

private var timer: Timer? = null
private var fileOutputStream: FileOutputStream? = null
private var fileChannel: FileChannel? = null
private var dataChunkSize: Long = 0
private var outputFile: File? = null

/**
* Starts audio recording.
* Creates output file and timer if not already initialized (supports reusability).
*/
fun start() {
// Initialize file resources if not exists (for reusability after stop)
if (fileOutputStream == null) {
outputFile = createOutputFile()
fileOutputStream = FileOutputStream(outputFile)
fileChannel = fileOutputStream?.channel
writeWavHeader()
}

// Start timer to pull and write audio data every PULL_INTERVAL_MS
timer = fixedRateTimer(
name = "AudioRecorderTimer",
daemon = false,
initialDelay = 0L,
period = PULL_INTERVAL_MS.toLong()
) {
pullAndWriteAudio()
}
}

/**
* Stops audio recording and finalizes the WAV file.
* Updates the file header with actual data sizes and closes all file resources.
*/
fun stop() {
// Stop and cleanup timer
timer?.cancel()
timer = null

// Update RIFF ChunkSize field: total file size minus 8 bytes
fileChannel?.position(4)
fileChannel?.write(
ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt((dataChunkSize + 36).toInt())
.flip() as ByteBuffer
)

// Update data Subchunk2Size field: actual audio data size
fileChannel?.position(40)
fileChannel?.write(
ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(dataChunkSize.toInt())
.flip() as ByteBuffer
)

// Close file resources
fileChannel?.close()
fileOutputStream?.close()
fileChannel = null
fileOutputStream = null
}

/**
* Creates a new WAV file with a timestamp in the external files directory.
*/
private fun createOutputFile(): File {
val timestamp = System.currentTimeMillis()
val file = File(context.getExternalFilesDir(null), "audio_record_$timestamp.wav")
if (!file.exists()) {
file.createNewFile()
}
return file
}

/**
* Writes WAV file header with format information.
* The ChunkSize and data size fields are initialized to 0 and updated in stop().
*/
private fun writeWavHeader() {
dataChunkSize = 0

val bitDepth: Short = when (SAMPLE_TYPE) {
PlanetKitAudioSampleType.SIGNED_SHORT_16 -> 16
else -> 32
}

// Prepare fmt chunk parameters in little endian byte order
// 14 bytes: NumChannels(2) + SampleRate(4) + ByteRate(4) + BlockAlign(2) + BitsPerSample(2)
val littleBytes = ByteBuffer.allocate(14)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort(CHANNEL_COUNT.toShort()) // NumChannels
.putInt(SAMPLE_RATE.sampleRate) // SampleRate
.putInt(SAMPLE_RATE.sampleRate * CHANNEL_COUNT * (bitDepth / 8)) // ByteRate
.putShort((CHANNEL_COUNT * (bitDepth / 8)).toShort()) // BlockAlign
.putShort(bitDepth) // BitsPerSample
.array()

// Write WAV file header (44 bytes total)
fileOutputStream?.write(
byteArrayOf(
// RIFF header (12 bytes)
'R'.code.toByte(), 'I'.code.toByte(), 'F'.code.toByte(), 'F'.code.toByte(), // ChunkID
0, 0, 0, 0, // ChunkSize = file_size - 8 (updated at position 4 on stop)
'W'.code.toByte(), 'A'.code.toByte(), 'V'.code.toByte(), 'E'.code.toByte(), // Format
// fmt chunk (24 bytes)
'f'.code.toByte(), 'm'.code.toByte(), 't'.code.toByte(), ' '.code.toByte(), // Subchunk1ID
16, 0, 0, 0, // Subchunk1Size = 16 for PCM
1, 0, // AudioFormat (PCM = 1)
littleBytes[0], littleBytes[1], // NumChannels
littleBytes[2], littleBytes[3], littleBytes[4], littleBytes[5], // SampleRate
littleBytes[6], littleBytes[7], littleBytes[8], littleBytes[9], // ByteRate
littleBytes[10], littleBytes[11], // BlockAlign
littleBytes[12], littleBytes[13], // BitsPerSample
// data chunk header (8 bytes)
'd'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'a'.code.toByte(), // Subchunk2ID
0, 0, 0, 0 // Subchunk2Size = audio data size (updated at position 40 on stop)
)
)
}

/**
* Pulls audio data from PlanetKit and writes it to the WAV file.
* Called periodically by the timer.
*/
private fun pullAndWriteAudio() {
try {
// Pull audio frame from PlanetKit
val frameData = getFrameData(SAMPLE_RATE, SAMPLE_TYPE, SAMPLE_COUNT)

// Write audio data to file and track total size
dataChunkSize += frameData.getSize()
fileChannel?.write(frameData.getBuffer())

} catch (e: Exception) {
e.printStackTrace()
}
}
}

カスタムオーディオシンクを使用する

PlanetKitCustomAudioSinkオブジェクトを引数として、PlanetKitCallまたはPlanetKitConferencesetCustomAudioSink()を呼び出し、カスタムオーディオシンクを開始します。

カスタムオーディオシンクが開始されると、カスタムオーディオシンクから呼び出されるgetFrameData()メソッドを通じてオーディオデータを受信します。

val customAudioSink = MyCustomAudioRecorder(context)

// For 1-to-1 call
fun useCustomSinkSourceForCall(call: PlanetKitCall) {
call.setCustomAudioSink(customAudioSink)
// Execute the start logic you created.
customAudioSink.start()
}

// For group call
fun useCustomSinkForConference(conference: PlanetKitConference) {
conference.setCustomAudioSink(customAudioSink)
// Execute the start logic you created.
customAudioSink.start()
}

カスタムオーディオシンクをスピーカーに戻しておく

カスタムオーディオシンクをスピーカーに戻すには、PlanetKitCallまたはPlanetKitConferenceclearCustomAudioSink()を呼び出してカスタムオーディオシンクを停止します。

val customAudioSink = MyCustomAudioRecorder(context)

// For 1-to-1 call
fun useDefaultAudioSinkForCall(call: PlanetKitCall) {
call.clearCustomAudioSink()
// Execute the stop logic you created.
customAudioSink.stop()
}

// For group call
fun useDefaultAudioSinkForConference(conference: PlanetKitConference) {
conference.clearCustomAudioSink()
// Execute the stop logic you created.
customAudioSink.stop()
}

主な考慮事項

カスタムオーディオデバイス機能を使用する際に考慮すべき事項は次のとおりです。

オーディオフォーマット

  • サンプルレート:48kHzまたは16kHz
  • チャンネル:モノ(1チャンネル)
  • サンプルタイプ:16-bit PCM

パフォーマンスの最適化

  • パフォーマンスのためにメモリーの割り当てと解除を最小限に抑える必要があります。
  • オーディオ処理は必ずバックグラウンドスレッドで行ってください。

カスタムオーディオデバイス使用時のAEC設定

AEC(acoustic echo cancellation)は、スピーカーから出力された音がマイクに再入力されて発生するエコーを除去する技術です。PlanetKitが提供するAEC関連の機能には、VQE制御のAECアプリケーションAECリファレンスがあります。一般的には、AECに関連して別途の設定は必要ありませんが、カスタムオーディオデバイスを利用する場合は、状況に応じてAEC関連の設定を直接実行する必要があります。

AEC関連設定が必要な条件

アプリケーションの実装が以下の条件に該当する場合は、AEC関連の設定を直接実行する必要があります。

  • Case 1:VQE制御のAEC設定が必要な場合
    • 条件
      • 以下の条件のいずれかに該当する場合
        • カスタムオーディオソースからの実際のマイクデバイス以外の入力(例:WAVファイル、HTTP URLなど)を使用する場合
        • カスタムオーディオシンクからの実際のスピーカーデバイス以外の入力(例:ファイルレコーディング)を使用する場合
    • 必要設定
      • カスタムオーディオデバイスの使用開始後にsetAcousticEchoCancellerMode(PlanetKitVoiceProcessor.AcousticEchoCancellerMode.DISABLED)呼び出し
      • カスタムオーディオデバイスの使用終了後にsetAcousticEchoCancellerMode(PlanetKitVoiceProcessor.AcousticEchoCancellerMode.INTENSITY_RECOMMENDED)呼び出し
  • Case 2:アプリケーションAECリファレンス設定が必要な場合
    • 条件
      • PlanetKitから引き渡されたスピーカーデータをカスタムオーディオシンクで変調して出力する場合
        • ただし、マイク入力が実際のマイクデバイスの場合にのみ該当(入力がWAVファイルなど実際のエコーが入力できるデバイスでない場合は該当しない)
    • 必要設定
      • カスタムオーディオシンクの使用開始後にstartUserAcousticEchoCancellerReference()呼び出し
      • putUserAcousticEchoCancellerReference()でスピーカーに出力したい音源を提供
      • カスタムオーディオシンクの使用終了後にstopUserAcousticEchoCancellerReference()呼び出し

Case 1:VQE制御のAEC設定が必要な場合

カスタムオーディオソースまたはカスタムオーディオシンクを利用して実際のマイクとスピーカー以外の入力または出力を使用している場合は、通話セッションクラスであるPlanetKitCallおよびPlanetKitConferenceで提供されるVQE機能の1つであるAECの使用を中止して、通話品質を向上させることができます。PlanetKitSendVoiceProcessorsetAcousticEchoCancellerMode()を呼び出してAECモードを設定できます。

カスタムオーディオソースクラスを実装する

カスタムオーディオソースを利用してオーディオファイルを入力として使用するためのクラスを実装します。

  • 通話に接続された後、オーディオデータを送信するために定期的にpostFrameData()を呼び出します。
import com.linecorp.planetkit.audio.PlanetKitCustomAudioSource
import com.linecorp.planetkit.audio.AudioFrame
import com.linecorp.planetkit.audio.PlanetKitAudioSampleType
import com.linecorp.planetkit.audio.PlanetKitAudioSampleRate
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.Timer
import kotlin.math.PI
import kotlin.math.sin
import kotlin.concurrent.fixedRateTimer

// Example implementation of a custom audio source
class MyCustomAudioSource : PlanetKitCustomAudioSource() {

// Audio configuration settings
private val sampleType = PlanetKitAudioSampleType.SIGNED_SHORT_16
private val sampleRate = PlanetKitAudioSampleRate.SAMPLE_RATE_48K

// Streaming state management
private var isStreaming = false
private var timer: Timer? = null

// Start audio streaming
fun start() {
if (!isStreaming) {
isStreaming = true

timer = fixedRateTimer(
name = "CustomAudioSourceTimer",
initialDelay = 0L,
period = 20L // 20ms cycle
) {
if (isStreaming) {
generateAndSendAudioFrames()
}
}
}
}

// Stop audio streaming
fun stop() {
isStreaming = false
timer?.cancel()
timer = null
}

// Main loop that generates and sends audio frames every 20ms
private fun generateAndSendAudioFrames() {
// Generate audio data (example: sine wave)
val audioData = generateSineWave()

// Create ByteBuffer
val buffer = ByteBuffer.allocate(audioData.size * 2).order(ByteOrder.LITTLE_ENDIAN)
for (sample in audioData) {
buffer.putShort(sample)
}

buffer.flip()

// Create and send AudioFrame
val audioFrame = createAudioFrame(buffer)
postFrameData(audioFrame)
audioFrame.release()
}

private fun generateSineWave(): ShortArray {
val samples = ShortArray(960) // 20ms at 48kHz = 960 samples
for (i in samples.indices) {
val time = i.toDouble() / sampleRate.sampleRate
val amplitude = (Short.MAX_VALUE * 0.3).toInt()
samples[i] = (amplitude * sin(2 * PI * 440 * time)).toInt().toShort()
}
return samples
}

private fun createAudioFrame(buffer: ByteBuffer): AudioFrame {
return object : AudioFrame {
override fun getBuffer(): ByteBuffer = buffer
override fun getSampleType(): PlanetKitAudioSampleType = sampleType
override fun getSize(): Long = buffer.remaining().toLong()
override fun getSamplingRate(): PlanetKitAudioSampleRate = sampleRate
override fun getTimestamp(): Long = System.nanoTime()
override fun release() {}
}
}
}

カスタムオーディオソースを使用する

カスタムオーディオソースを使用するには、実装したカスタムオーディオソースのオブジェクトを引数としてPlanetKitCallまたはPlanetKitConferencesetCustomAudioSource()を呼び出し、PlanetKitVoiceProcessor.AcousticEchoCancellerMode.DISABLEDを引数としてPlanetKitSendVoiceProcessorクラスのsetAcousticEchoCancellerMode()を呼び出してAEC を無効に設定します。

val customAudioSource = MyCustomAudioSource()

// For 1-to-1 call
fun useCustomAudioSourceForCall(call: PlanetKitCall) {
val customAudioSource = MyCustomAudioSource()
call.setCustomAudioSource(customAudioSource)
customAudioSource.start()

// Disable AEC
call.sendVoiceProcessor.setAcousticEchoCancellerMode(
PlanetKitVoiceProcessor.AcousticEchoCancellerMode.DISABLED
)
}

カスタムオーディオソースをマイクに戻しておく

カスタムオーディオソースをマイクに戻しておくには、PlanetKitCallまたはPlanetKitConferenceclearCustomAudioSource()を呼び出し、PlanetKitVoiceProcessor.AcousticEchoCancellerMode.INTENSITY_RECOMMENDEDを引数としてPlanetKitSendVoiceProcessorクラスのsetAcousticEchoCancellerMode()を呼び出してAECを有効に設定します。

val customAudioSource = MyCustomAudioSource()

// For 1-to-1 call
fun useDefaultAudioSourceForCall(call: PlanetKitCall) {
call.clearCustomAudioSource()
customAudioSource.stop()

// Enable AEC by default
call.sendVoiceProcessor.setAcousticEchoCancellerMode(
PlanetKitVoiceProcessor.AcousticEchoCancellerMode.INTENSITY_RECOMMENDED
)
}

Case 2:アプリケーションAECリファレンス設定が必要な場合

通話セッションクラスであるPlanetKitCallおよびPlanetKitConferenceで提供されるstartUserAcousticEchoCancellerReference()stopUserAcousticEchoCancellerReference()を利用して、AECリファレンスデータを使用するかどうかを設定できます。AECリファレンスデータを有効にした後、通話セッションクラスのputUserAcousticEchoCancellerReference()を利用して変更されたオーディオデータを提供します。

カスタムオーディオシンククラスを実装する

カスタムオーディオシンクを利用してオーディオ出力データを変更するためのクラスを実装します。

  • 通話に接続された後、受信されたオーディオデータを取得するために定期的にgetFrameData()を呼び出します。
  • オーディオデータを加工し、これを引数としてputUserAcousticEchoCancellerReference()を呼び出します。
/**
* Example implementation of PlanetKitCustomAudioSink.
*
* This example demonstrates how to:
* - Pull audio data from PlanetKit
* - Render audio using Android AudioTrack
* - Handle acoustic echo cancellation (AEC) reference
*/
class MyCustomAudioSink(private val context: Context) : PlanetKitCustomAudioSink() {

companion object {
// Configure audio parameters
private val SAMPLE_RATE = PlanetKitAudioSampleRate.SAMPLE_RATE_16K
private const val PULL_INTERVAL_MS = 20
private val SAMPLE_TYPE = PlanetKitAudioSampleType.SIGNED_SHORT_16
private val SAMPLE_COUNT = (PULL_INTERVAL_MS * SAMPLE_RATE.sampleRate) / 1000
}

private var timer: Timer? = null
private var audioTrack: AudioTrack? = null

fun start() {
// Create AudioTrack if not exists (for reusability after stop)
if (audioTrack == null) {
audioTrack = createAudioTrack()
}

// Start audio playback
audioTrack?.play()

// Create timer to pull audio data periodically
timer = fixedRateTimer(
name = "AudioPullTimer",
daemon = false,
initialDelay = 0L,
period = PULL_INTERVAL_MS.toLong()
) {
pullAndRenderAudio()
}
}

fun stop() {
// Stop timer
timer?.cancel()
timer = null

// Stop and release AudioTrack
audioTrack?.flush()
audioTrack?.stop()
audioTrack?.release()
audioTrack = null
}
private fun createAudioTrack(): AudioTrack {
val bufferSize = AudioTrack.getMinBufferSize(
SAMPLE_RATE.sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT
)

val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

return AudioTrack(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build(),
AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE.sampleRate)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.build(),
bufferSize,
AudioTrack.MODE_STREAM,
audioManager.generateAudioSessionId()
)
}

private fun pullAndRenderAudio() {
try {
// Pull audio data from PlanetKit
val frameData = getFrameData(SAMPLE_RATE, SAMPLE_TYPE, SAMPLE_COUNT)

// Write to AudioTrack
audioTrack?.write(
frameData.getBuffer(),
frameData.getSize().toInt(),
AudioTrack.WRITE_BLOCKING
)

// Provide AEC reference for echo cancellation
putUserAcousticEchoCancellerReference(frameData)

} catch (e: Exception) {
// Handle error
e.printStackTrace()
}
}
}

カスタムオーディオシンクを使用する

カスタムオーディオシンクを使用するには、実装したカスタムオーディオシンクのオブジェクトを引数としてPlanetKitCallまたはPlanetKitConferencesetCustomAudioSink()を呼び出し、カスタムオーディオシンクを開始してから、アプリケーションAECリファレンスの使用を開始するように通話セッションクラスのstartUserAcousticEchoCancellerReference()を呼び出します。

val customAudioSink = MyCustomAudioSink(context)

// For 1-to-1 call
fun useCustomAudioSinkForCall(call: PlanetKitCall) {
call.setCustomAudioSink(customAudioSink)
customAudioSink.start()

call.startUserAcousticEchoCancellerReference()
}

カスタムオーディオシンクをスピーカーに戻しておく

カスタムオーディオシンクをスピーカーに戻しておくには、PlanetKitCallまたはPlanetKitConferenceclearCustomAudioSink()を呼び出し、カスタムオーディオシンクを中止してから、アプリケーションAECリファレンスの使用を中止するように通話セッションクラスのstopUserAcousticEchoCancellerReference()を呼び出します。

val customAudioSink = MyCustomAudioSink(context)

// For 1-to-1 call
fun useDefaultAudioSinkForCall(call: PlanetKitCall) {
call.clearCustomAudioSink()
customAudioSink.stop()

call.stopUserAcousticEchoCancellerReference()
}

関連API

カスタムオーディオデバイス機能とこれに関連するAPIは以下のとおりです。

共通

1対1通話

グループ通話