見出し画像

Androidアプリ開発入門 (5) - Porcupine

Androidアプリでの「Porcupine」による「ウェイクワード検出」の実装方法をまとめました。

・API 29: Android 10 (Q)

前回

1. Porcupine

Porcupine」は、「Picovoice」によって開発された、高精度かつ軽量なウェイクワードエンジンです。これにより、GoogleホームやAlexaのような、常に音声を聞くことができるアプリケーションを構築することができます。

2. Porcupineのインストール

Porcupineのインストールの手順は、次のとおりです。

(1) プロジェクトのbuild.gradleにMavenCentraの参照があることを確認。

    repositories {
       mavenCentral()
    }

(2) モジュールのbuild.gradleに次の参照を追加。
「Purcupineパッケージ」と「通知パッケージ」を追加しています。

dependencies {
   implementation 'ai.picovoice:porcupine-android:1.9.0'
   implementation "com.android.support:support-compat:28.0.0"
}

3. AndroidManifest.xml

「AndroidManifest.xml」に以下の項目を設定します。

・android.permission.FOREGROUND_SERVICE : フォアグラウンドサービスのパーミッション
・android.permission.RECORD_AUDIO
: マイクのパーミッション
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />

これから作成する「PorcupineService」の定義も追加します。

<service android:name=".PorcupineService" android:exported="false" />

ユーザー権限の許可には「PermissionsDispatcher」を使っています。

4. UI

今回は、「Button」(id@button)を1つ配置します。

画像1

5. サービスを開始するアクティビティ

サービスを開始するアクティビティのコードは、次のとおりです。
常駐したいので、「フォアグラウンドサービス」として実装しています。

package net.npaka.porcupineserviceex
import android.Manifest
import android.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import android.widget.Button
import android.content.Intent
import android.view.View
import permissions.dispatcher.*

@RuntimePermissions
class MainActivity : AppCompatActivity(), View.OnClickListener {
    private var button: Button? = null

//====================
// ライフサイクル
//====================
    // 起動時に呼ばれる
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 参照
        this.button = findViewById(R.id.button)
        this.button!!.setOnClickListener(this)
        this.button!!.text = "Purcupineサービスの開始"
    }


//====================
// パーミッション
//====================
    // 許可された時に呼ばれる
    @NeedsPermission(Manifest.permission.RECORD_AUDIO)
    fun setupRecognizer() {
        startService()
    }

    // 説明が必要な時に呼ばれる
    @OnShowRationale(Manifest.permission.RECORD_AUDIO)
    fun onCameraShowRationale(request: PermissionRequest) {
        AlertDialog.Builder(this)
            .setPositiveButton("許可") { _, _ -> request.proceed() }
            .setNegativeButton("許可しない") { _, _ -> request.cancel() }
            .setCancelable(false)
            .setMessage("マイクを利用します")
            .show()
    }

    // 拒否された時に呼ばれる
    @OnPermissionDenied(Manifest.permission.RECORD_AUDIO)
    fun onCameraPermissionDenied() {
        Toast.makeText(this, "拒否されました", Toast.LENGTH_SHORT).show()
    }

    // 「今後表示しない」が選択された時に呼ばれる
    @OnNeverAskAgain(Manifest.permission.RECORD_AUDIO)
    fun onCameraNeverAskAgain() {
        Toast.makeText(this, "「今後表示しない」が選択されました", Toast.LENGTH_SHORT).show()
    }


//====================
// 操作
//====================
    // クリック時に呼ばれる
    override fun onClick(v: View) {
        if (this.button!!.text == "Purcupineサービスの開始") {
            this.button!!.text = "Purcupineサービスの停止"
            setupRecognizerWithPermissionCheck()
        } else {
            this.button!!.text = "Purcupineサービスの開始"
            stopService()
        }
    }

    // サービスの開始
    private fun startService() {
        val intent = Intent(this, PorcupineService::class.java)
        this.startForegroundService(intent)
    }

    // サービスの停止
    private fun stopService() {
        val intent = Intent(this, PorcupineService::class.java)
        stopService(intent)
    }
}

6. サービス

サービスのコードは、次のとおりです。

package net.npaka.porcupineserviceex
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.annotation.Nullable
import ai.picovoice.porcupine.PorcupineManager
import ai.picovoice.porcupine.PorcupineException
import ai.picovoice.porcupine.Porcupine
import android.util.Log
import androidx.core.app.NotificationManagerCompat

// Porcupineサービス
class PorcupineService : Service() {
    private var porcupineManager: PorcupineManager? = null

//====================
// ライフサイクル
//====================
    // 開始時に呼ばれる
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 通知チャンネルの生成
        createNotificationChannel()

        // 通知の生成
        val notification = createNotification("PorcupineService実行中")

        // フォアグラウンドの開始
        startForeground(1234, notification)

        // Purcupineの準備
        try {
            porcupineManager = PorcupineManager.Builder()
                .setKeyword(Porcupine.BuiltInKeyword.COMPUTER)
                .setSensitivity(0.7f)
                .build(
                    applicationContext
                ) { keywordIndex: Int -> onDetect() }
            porcupineManager!!.start()
        } catch (e: PorcupineException) {
            Log.d("debug", e.toString())
        }
        return START_STICKY
    }

    // バインド時に呼ばれる
    @Nullable
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    // 破棄時に呼ばれる
    override fun onDestroy() {
        // Purcupineの破棄
        try {
            porcupineManager!!.stop()
            porcupineManager!!.delete()
        } catch (e: PorcupineException) {
            Log.d("debug", e.toString())
        }
        super.onDestroy()
    }

    // 検出時に呼ばれる
    fun onDetect() {
        sendNotification("Wake Up!", 1234)
    }


//====================
// 通知
//====================
    // 通知チャンネルの生成
    private fun createNotificationChannel() {
        // 通知チャンネルの生成
        val channel = NotificationChannel(
            "Purcupine",
            "Purcupine",
            NotificationManager.IMPORTANCE_DEFAULT)

        // システムに通知チャンネルを登録
        val notificationManager: NotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }

    // 通知の生成
    private fun createNotification(text: String) : Notification {
        return Notification.Builder(this, "Purcupine")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentText(text)
            .build()
    }

    // 通知の送信
    private fun sendNotification(text: String, notifId: Int) {
        var notification = Notification.Builder(this, "Purcupine")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentText(text)
            .build()
        with (NotificationManagerCompat.from(this)) {
            notify(notifId, notification)
        }
    }
}

7. 実行

サービスを開始すると、ステータスバーの右端にマイクマークが表示されます。「Computer」と発話すると、通知に「Wake UP!」と表示されます。

画像2

ロック画面でも表示されます。

画像3

8. 関連

次回



この記事が気に入ったらサポートをしてみませんか?