Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vosk stopped recognizing speech when started from background #223

Open
iamuseful opened this issue Sep 13, 2024 · 0 comments
Open

Vosk stopped recognizing speech when started from background #223

iamuseful opened this issue Sep 13, 2024 · 0 comments

Comments

@iamuseful
Copy link

Good day and thanks for this solution

I implemented Vosk in a service in android.
when I start it from the foreground, it works fine in recognizing speech (even though for few minutes)
my main challenge now is that, it doesnt recognize any speech at all when I start the service from background using WorkManager
and all logs are the same (fore and back scenarios).

I have tried audiofocus, tried reset of speechservice and recognizer. none works

class VoiceRecognitionService : Service() {

    private lateinit var model: Model
    private lateinit var recognizer: Recognizer
    private var speechService: SpeechService? = null
    private lateinit var ei: MyExternalIntegrator
    private lateinit var modelReadyDeferred: CompletableDeferred<Boolean>
    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val tag = "SpeechService"

    private var audioManager: AudioManager? = null
    private var focusRetryCount = 0
    private val maxFocusRetries = 5

    companion object {
        private const val NOTIFICATION_ID = 1
        private const val ACTION_STOP_SERVICE = "ACTION_STOP_SERVICE"
    }

    override fun onCreate() {
        super.onCreate()
        ei = MyExternalIntegrator(this)
        Log.i(tag, "onCreate")
        // Initialize audio manager
        audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
        initializeVosk()
        LibVosk.setLogLevel(LogLevel.INFO)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)

        Log.i(tag, "onStartCommand")
        if (intent?.action == ACTION_STOP_SERVICE) {
            stopSelf()
            SharedPrefs(applicationContext).setPrefValue("listen_state", false)
            return START_NOT_STICKY
        }

        // Ensure audio focus before starting the service
        if (!requestAudioFocus()) {
            Log.e(tag, "Failed to gain audio focus, retrying")
            retryAudioFocus()
            return START_NOT_STICKY
        }

        // Start foreground service
        startForeground(NOTIFICATION_ID, createNotification())

        serviceScope.launch {
            modelReadyDeferred.await() // Ensure Vosk model is ready
            startListening() // Start listening after model is ready
        }

        return START_STICKY
    }

    private fun requestAudioFocus(): Boolean {
        Log.i(tag, "Requesting audio focus")
        val focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT

        val result = audioManager?.requestAudioFocus(
            audioFocusChangeListener,
            AudioManager.STREAM_MUSIC,
            focusRequest
        )

        return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
    }

    private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
        Log.i(tag, "audioFocusChangeListener: $focusChange")
        when (focusChange) {
            AudioManager.AUDIOFOCUS_LOSS -> {
                Log.e(tag, "Audio focus lost. Stopping service.")
                stopSelf()
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                Log.i(tag, "Audio focus lost temporarily. Pausing...")
                speechService?.stop()
                retryAudioFocus() // Try to regain audio focus
            }
            AudioManager.AUDIOFOCUS_GAIN -> {
                Log.i(tag, "Audio focus gained. Resuming listening...")
                startListening() // Resume listening when focus is gained
            }
        }
    }

    private fun retryAudioFocus() {
        Log.i(tag, "Retrying audio focus")
        serviceScope.launch {
            var retries = 0
            while (retries < maxFocusRetries) {
                delay(2000) // Wait 2 seconds before trying again
                if (requestAudioFocus()) {
                    Log.i(tag, "Audio focus regained after retry ($retries/$maxFocusRetries)")
                    focusRetryCount = 0
                    startListening() // Resume listening once focus is regained
                    break
                } else {
                    retries++
                    Log.e(tag, "Retrying audio focus ($retries/$maxFocusRetries)")
                }
            }

            if (retries == maxFocusRetries) {
                Log.e(tag, "Failed to regain audio focus after max of $maxFocusRetries retries. Stopping service.")
                stopSelf() // Stop service if we can't regain audio focus
            }
        }
    }

    private fun createNotification(): Notification {
        ...
    }

    private fun initializeVosk() {
        Log.i(tag, "Initializing Vosk model")
        modelReadyDeferred = CompletableDeferred()

        serviceScope.launch {
            var retries = 0
            val maxRetries = 5

            while (retries < maxRetries) {
                try {
                    val modelPath = copyAssetFolder(applicationContext, "vosk-model-small-en-us-0.15", "${applicationContext.filesDir}/vosk")
                    model = Model(modelPath)
                    recognizer = Recognizer(model, 16000.0f)
                    modelReadyDeferred.complete(true)
                    Log.i(tag, "Vosk model initialized successfully")
                    break
                } catch (e: Exception) {
                    retries++
                    Log.e(tag, "Failed to initialize Vosk model. Retrying ($retries/$maxRetries)...")
                    delay(2000)
                }
            }

            if (retries == maxRetries) {
                modelReadyDeferred.completeExceptionally(Exception("Failed to initialize Vosk model after $maxRetries retries"))
            }
        }
    }

    private fun startListening() {
        Log.i(tag, "Starting listening")


        if (!this::recognizer.isInitialized) {
            Log.e(tag, "Recognizer not initialized. Attempting to initialize...")
            serviceScope.launch {
                try {
                    initializeVosk()
                    initializeSpeechService()
                } catch (e: Exception) {
                    Log.e(tag, "Error initializing Vosk or starting listening: ${e.message}")
                }
            }
        } else {
            initializeSpeechService()
        }
    }

    private fun initializeSpeechService() {
        if (speechService == null) {
            Log.i(tag, "Initializing speech service")
            speechService = SpeechService(recognizer, 16000.0f)
        }

        speechService?.reset() //todo remove
        if (speechService?.startListening(resultListener) == false) {
            Log.i(tag, "Starting speech service listening...")
            speechService?.startListening(resultListener)
        } else {
            Log.i(tag, "Speech service is already listening")
        }
    }

    private val resultListener = object : RecognitionListener {
        override fun onPartialResult(hypothesis: String?) {
            // Handle partial results if needed
        }

        override fun onResult(hypothesis: String?) {
            Log.i(tag, "Recognized speech: $hypothesis")
            
        }

        override fun onFinalResult(hypothesis: String?) {
            // Handle final results if needed
        }

        override fun onError(e: Exception?) {
            Log.e(tag, "Recognition error: ${e?.message}")
        }

        override fun onTimeout() {
            Log.i(tag, "Listening timeout, restarting...")
            startListening()
        }
    }

    override fun onDestroy() {
        Log.i(tag, "Service destroyed")
        serviceScope.cancel() // Cancel all ongoing coroutines
        audioManager?.abandonAudioFocus(audioFocusChangeListener) // Release audio focus
        speechService?.stop() // Stop speech service
        speechService?.shutdown()
        if (this::recognizer.isInitialized) { recognizer?.close() }
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        restartService()
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant