Implement Advanced VAD Strategies for Improved Speech Detection

Resolved 💬 2 comments Opened Oct 20, 2025 by sanchitmonga22 Closed Oct 20, 2025

Priority

HIGH

Context

The iOS SDK has multiple VAD strategies (energy-based, ML-based, hybrid). The Kotlin SDK only has basic VAD implementation. Advanced strategies improve detection accuracy.

Current State

  • Status: Only basic VAD exists
  • Impact: Reduced speech detection accuracy
  • iOS Has: Multiple VAD strategies with strategy selection

iOS Reference Implementation

  • Strategies: sdk/runanywhere-swift/Sources/RunAnywhere/Components/VAD/Strategies/

iOS Architecture Pattern

public protocol VADStrategy: Sendable {
    func detect(audioData: Data) async -> VADResult
}

public final class EnergyBasedVAD: VADStrategy { ... }
public final class MLBasedVAD: VADStrategy { ... }
public final class HybridVAD: VADStrategy { ... }

Implementation Plan

1. Define Strategy Interfaces (commonMain)

// commonMain/kotlin/com/runanywhere/sdk/components/vad/strategies/VADStrategy.kt
interface VADStrategy {
    suspend fun detect(audioData: ByteArray): VADResult
    val strategyType: StrategyType
}

data class VADResult(
    val isSpeech: Boolean,
    val confidence: Float,
    val energyLevel: Float? = null
)

enum class StrategyType {
    ENERGY_BASED,
    ML_BASED,
    HYBRID
}

// commonMain/kotlin/com/runanywhere/sdk/components/vad/strategies/EnergyBasedVAD.kt
class EnergyBasedVAD(
    private val threshold: Float = 0.03f
) : VADStrategy {
    
    override suspend fun detect(audioData: ByteArray): VADResult {
        val energy = calculateEnergy(audioData)
        val isSpeech = energy > threshold
        
        return VADResult(
            isSpeech = isSpeech,
            confidence = if (isSpeech) minOf(energy / threshold, 1.0f) else 0.0f,
            energyLevel = energy
        )
    }
    
    override val strategyType = StrategyType.ENERGY_BASED
    
    private fun calculateEnergy(audioData: ByteArray): Float {
        var sum = 0.0
        for (i in audioData.indices step 2) {
            val sample = ((audioData[i+1].toInt() shl 8) or (audioData[i].toInt() and 0xFF)).toShort()
            sum += sample * sample
        }
        return (sum / (audioData.size / 2)).toFloat()
    }
}

// commonMain/kotlin/com/runanywhere/sdk/components/vad/strategies/HybridVAD.kt
class HybridVAD(
    private val energyStrategy: EnergyBasedVAD,
    private val mlStrategy: VADStrategy?
) : VADStrategy {
    
    override suspend fun detect(audioData: ByteArray): VADResult {
        val energyResult = energyStrategy.detect(audioData)
        
        // If energy is too low, no need for ML
        if (!energyResult.isSpeech) {
            return energyResult
        }
        
        // Use ML for refinement
        val mlResult = mlStrategy?.detect(audioData)
        
        return if (mlResult != null) {
            VADResult(
                isSpeech = mlResult.isSpeech,
                confidence = (energyResult.confidence + mlResult.confidence) / 2,
                energyLevel = energyResult.energyLevel
            )
        } else {
            energyResult
        }
    }
    
    override val strategyType = StrategyType.HYBRID
}

Files to Create/Modify

New Files (commonMain)

  • [ ] components/vad/strategies/VADStrategy.kt
  • [ ] components/vad/strategies/EnergyBasedVAD.kt
  • [ ] components/vad/strategies/HybridVAD.kt
  • [ ] components/vad/models/VADResult.kt

Files to Modify

  • [ ] components/vad/VADComponent.kt (use strategies)

Testing Requirements

  • [ ] Unit tests for each strategy
  • [ ] Accuracy tests with real audio
  • [ ] Performance benchmarks
  • [ ] Strategy switching tests

Success Criteria

  • [ ] Energy-based VAD works correctly
  • [ ] Hybrid VAD improves accuracy
  • [ ] Strategy selection is configurable
  • [ ] Performance is acceptable

Estimated Effort

5-7 days

Dependencies

  • Depends on: VADComponent (exists)

🤖 Generated with Claude Code

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗