独自(マイクとか音声ファイルとか)で音声データを渡して音声認識をしたい場合を想定してGCPで試してみました
課金したくないのでv1で試しましたが、v1使うくらいならAndroid SDKを使った方がいいかなって感じです
Android向けのサンプルプロジェクトはメンテされていなくて古いのでこちらを参考にした方がよいです
https://cloud.google.com/speech-to-text/docs/streaming-recognize?hl=ja
録音の実装は013. AudioRecordで作ったものを使用しています
GCPの設定
GCPでプロジェクトを追加してCloud Speech-to-Text APIを有効にします
・APIキーの取得
APIとサービスで認証情報の認証情報の作成でAPIキーを作ります

・サービスアカウントキー取得
軽く試したいだけなのでサンプル通りサービスアカウントキーをjsonで取得します
IAMと管理のサービスアカウントでキーの鍵の追加を選択してjsonをダウンロードします
取得したjsonはcredential.jsonにリネームして”key” : “(APIキー)”を追加して
対象プロジェクトのres/rawに置きます

実装
とりあえず試すだけの最低限の実装です
発話検出とかないので手動で録音止めてください
止めないと無限に音声データ送信しつづけるので課金が怖いです
認識結果のisFinalはsend中はtrueにならないのでこのフラグを見て録音止めることもできない
課金したくないからv2は試してないけどv2ならいい感じにできるのかも
com.google.cloud:google-cloud-speech 4.43.0
io.grpc:grpc-okhttp 1.66.0<manifest
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/LICENSE"
excludes += "/META-INF/INDEX.LIST"
excludes += "/META-INF/DEPENDENCIES"
}
}import android.Manifest
import com.google.cloud.speech.v1.RecognitionConfig
import com.google.cloud.speech.v1.SpeechClient
import com.google.cloud.speech.v1.SpeechSettings
import com.google.cloud.speech.v1.StreamingRecognitionConfig
import com.google.cloud.speech.v1.StreamingRecognizeRequest
import com.google.cloud.speech.v1.StreamingRecognizeResponse
class MainActivity : ComponentActivity() {
val locationPermissionRequest by lazy {
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions.getOrDefault(Manifest.permission.RECORD_AUDIO, false) -> {}
else -> {}
}
}
}
val recorder = AudioRecorder()
val speechClient by lazy {
applicationContext.resources.openRawResource(R.raw.credential).use {
SpeechClient.create(SpeechSettings.newBuilder()
.setCredentialsProvider { GoogleCredentials.fromStream(it) }
.build())
}
}
var requestStream: ClientStream<StreamingRecognizeRequest?>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
locationPermissionRequest.launch(
arrayOf(Manifest.permission.RECORD_AUDIO)
)
setContent {
MaterialTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Screen(Modifier.padding(innerPadding))
}
}
}
}
override fun onDestroy() {
super.onDestroy()
recorder.stop()
requestStream?.closeSend()
speechClient.shutdown()
}
@Composable
fun Screen(modifier: Modifier = Modifier) {
val context = LocalContext.current
val isRecording by recorder.isRecording.collectAsState()
val isFirstRequest = AtomicBoolean(true)
Row(modifier, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Button(onClick = {
if (!recorder.isRecording.value) {
// closeSendした後作り直さないとIllegalStateException call was half-closedになる?
requestStream = speechClient.streamingRecognizeCallable()?.splitCall(object : ResponseObserver<StreamingRecognizeResponse> {
override fun onStart(controller: StreamController?) {}
override fun onResponse(response: StreamingRecognizeResponse?) {
response?.let {
Log.d("SpeechClient", "onResponse: ${it.toString()}")
val result = it.resultsList.first()
val alternative = result?.alternativesList?.first()
Log.d("SpeechClient", "onResponse: ${alternative?.transcript}")
}
}
override fun onError(t: Throwable?) {}
override fun onComplete() {
Log.d("SpeechClient", "onComplete")
}
})
isFirstRequest.set(true)
recorder.start(context) { buffer, size, index ->
val byteString = ByteString.copyFrom(buffer, 0, size)
val builder = StreamingRecognizeRequest.newBuilder()
.setAudioContent(byteString)
if (isFirstRequest.getAndSet(false)) {
builder.streamingConfig = StreamingRecognitionConfig.newBuilder()
.setConfig(RecognitionConfig.newBuilder()
.setLanguageCode("ja-JP")
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setSampleRateHertz(16000)
.setAudioChannelCount(1)
.build())
// 途中結果を受け取るかどうか
.setInterimResults(false)
// 一発話で解析を終了するかどうか
.setSingleUtterance(false)
.build()
}
requestStream?.send(builder.build())
}
}
else {
recorder.stop()
requestStream?.closeSend()
isFirstRequest.set(false)
}
}) {
Text(if (isRecording) "stop" else "start")
}
}
}
}認識結果は以下
v1では大した内容受け取れないみたいですね
results {
alternatives {
transcript: "432675317314314541607317311315"
confidence: 0.95148057
}
is_final: true
result_end_time {
seconds: 2
nanos: 160000000
}
language_code: "ja-jp"
}
total_billed_time {
seconds: 3
}
speech_event_time {
}
request_id: 5722919676389996625
今日はいい天気ですねAndroid Studio Koala 2024.1.1 Patch 1 built on July 11, 2024
