app/src/main/kotlin/com/honjimaku/subrep/Capture.kt (2534 bytes)
1 package com.honjimaku.subrep 2 3 import android.annotation.SuppressLint 4 import android.annotation.TargetApi 5 import android.media.AudioAttributes 6 import android.media.AudioFormat 7 import android.media.AudioPlaybackCaptureConfiguration 8 import android.media.AudioRecord 9 import android.media.MediaRecorder 10 import android.media.projection.MediaProjection 11 import android.os.Build 12 13 /** 14 * Where the sound comes from: the apps on this device, or the microphone. 15 * 16 * Both give 16 kHz mono 16-bit PCM, which is what speech recognition uses. The engine takes any 17 * rate, but a lower rate is less to send. 18 */ 19 object Capture { 20 21 const val SAMPLE_RATE = 16_000 22 23 /** 100 ms of sound: one frame to the engine. */ 24 const val FRAME_BYTES = SAMPLE_RATE * 2 / 10 25 26 /** 27 * The sound of the apps on this device, with the consent of the user in [projection]. 28 * Android gives the media, game and unknown streams. An app can refuse the capture 29 * (a video app with DRM, some music apps): then there is silence. 30 */ 31 @TargetApi(Build.VERSION_CODES.Q) 32 @SuppressLint("MissingPermission") // The screen checks RECORD_AUDIO before the service starts. 33 fun ofApps(projection: MediaProjection): AudioRecord { 34 val config = AudioPlaybackCaptureConfiguration.Builder(projection) 35 .addMatchingUsage(AudioAttributes.USAGE_MEDIA) 36 .addMatchingUsage(AudioAttributes.USAGE_GAME) 37 .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN) 38 .build() 39 return AudioRecord.Builder() 40 .setAudioFormat(format()) 41 .setBufferSizeInBytes(bufferBytes()) 42 .setAudioPlaybackCaptureConfig(config) 43 .build() 44 } 45 46 /** The microphone, tuned for speech: for a television or a talk in the room. */ 47 @SuppressLint("MissingPermission") 48 fun ofMicrophone(): AudioRecord = AudioRecord.Builder() 49 .setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION) 50 .setAudioFormat(format()) 51 .setBufferSizeInBytes(bufferBytes()) 52 .build() 53 54 private fun format() = AudioFormat.Builder() 55 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 56 .setSampleRate(SAMPLE_RATE) 57 .setChannelMask(AudioFormat.CHANNEL_IN_MONO) 58 .build() 59 60 /** One second of sound, or more when the device needs more. */ 61 private fun bufferBytes(): Int { 62 val least = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) 63 return maxOf(least, SAMPLE_RATE * 2) 64 } 65 }