1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.soundtrigger; 18 19 import android.annotation.Nullable; 20 import android.media.AudioFormat; 21 import android.media.audio.common.AidlConversion; 22 import android.media.audio.common.AudioConfig; 23 import android.media.soundtrigger.AudioCapabilities; 24 import android.media.soundtrigger.ConfidenceLevel; 25 import android.media.soundtrigger.ModelParameterRange; 26 import android.media.soundtrigger.Phrase; 27 import android.media.soundtrigger.PhraseRecognitionEvent; 28 import android.media.soundtrigger.PhraseRecognitionExtra; 29 import android.media.soundtrigger.PhraseSoundModel; 30 import android.media.soundtrigger.Properties; 31 import android.media.soundtrigger.RecognitionConfig; 32 import android.media.soundtrigger.RecognitionEvent; 33 import android.media.soundtrigger.RecognitionMode; 34 import android.media.soundtrigger.SoundModel; 35 import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; 36 import android.media.soundtrigger_middleware.RecognitionEventSys; 37 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; 38 import android.os.ParcelFileDescriptor; 39 import android.os.SharedMemory; 40 import android.system.ErrnoException; 41 42 import java.nio.ByteBuffer; 43 import java.util.Arrays; 44 import java.util.Locale; 45 import java.util.UUID; 46 47 /** @hide */ 48 public class ConversionUtil { aidl2apiModuleDescriptor( SoundTriggerModuleDescriptor aidlDesc)49 public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor( 50 SoundTriggerModuleDescriptor aidlDesc) { 51 Properties properties = aidlDesc.properties; 52 return new SoundTrigger.ModuleProperties( 53 aidlDesc.handle, 54 properties.implementor, 55 properties.description, 56 properties.uuid, 57 properties.version, 58 properties.supportedModelArch, 59 properties.maxSoundModels, 60 properties.maxKeyPhrases, 61 properties.maxUsers, 62 aidl2apiRecognitionModes(properties.recognitionModes), 63 properties.captureTransition, 64 properties.maxBufferMs, 65 properties.concurrentCapture, 66 properties.powerConsumptionMw, 67 properties.triggerInEvent, 68 aidl2apiAudioCapabilities(properties.audioCapabilities) 69 ); 70 } 71 aidl2apiRecognitionModes(int aidlModes)72 public static int aidl2apiRecognitionModes(int aidlModes) { 73 int result = 0; 74 if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) { 75 result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; 76 } 77 if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) { 78 result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; 79 } 80 if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) { 81 result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION; 82 } 83 if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) { 84 result |= SoundTrigger.RECOGNITION_MODE_GENERIC; 85 } 86 return result; 87 } 88 api2aidlRecognitionModes(int apiModes)89 public static int api2aidlRecognitionModes(int apiModes) { 90 int result = 0; 91 if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) { 92 result |= RecognitionMode.VOICE_TRIGGER; 93 } 94 if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) { 95 result |= RecognitionMode.USER_IDENTIFICATION; 96 } 97 if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) { 98 result |= RecognitionMode.USER_AUTHENTICATION; 99 } 100 if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) { 101 result |= RecognitionMode.GENERIC_TRIGGER; 102 } 103 return result; 104 } 105 106 api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel)107 public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) { 108 return api2aidlSoundModel(apiModel); 109 } 110 api2aidlSoundModel(SoundTrigger.SoundModel apiModel)111 public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) { 112 SoundModel aidlModel = new SoundModel(); 113 aidlModel.type = apiModel.getType(); 114 aidlModel.uuid = api2aidlUuid(apiModel.getUuid()); 115 aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid()); 116 byte[] data = apiModel.getData(); 117 aidlModel.data = byteArrayToSharedMemory(data, "SoundTrigger SoundModel"); 118 aidlModel.dataSize = data.length; 119 return aidlModel; 120 } 121 api2aidlUuid(UUID apiUuid)122 public static String api2aidlUuid(UUID apiUuid) { 123 return apiUuid.toString(); 124 } 125 api2aidlPhraseSoundModel( SoundTrigger.KeyphraseSoundModel apiModel)126 public static PhraseSoundModel api2aidlPhraseSoundModel( 127 SoundTrigger.KeyphraseSoundModel apiModel) { 128 PhraseSoundModel aidlModel = new PhraseSoundModel(); 129 aidlModel.common = api2aidlSoundModel(apiModel); 130 aidlModel.phrases = new Phrase[apiModel.getKeyphrases().length]; 131 for (int i = 0; i < apiModel.getKeyphrases().length; ++i) { 132 aidlModel.phrases[i] = api2aidlPhrase(apiModel.getKeyphrases()[i]); 133 } 134 return aidlModel; 135 } 136 api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase)137 public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) { 138 Phrase aidlPhrase = new Phrase(); 139 aidlPhrase.id = apiPhrase.getId(); 140 aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.getRecognitionModes()); 141 aidlPhrase.users = Arrays.copyOf(apiPhrase.getUsers(), apiPhrase.getUsers().length); 142 aidlPhrase.locale = apiPhrase.getLocale().toLanguageTag(); 143 aidlPhrase.text = apiPhrase.getText(); 144 return aidlPhrase; 145 } 146 aidl2apiPhrase(Phrase aidlPhrase)147 public static SoundTrigger.Keyphrase aidl2apiPhrase(Phrase aidlPhrase) { 148 return new SoundTrigger.Keyphrase(aidlPhrase.id, 149 aidl2apiRecognitionModes(aidlPhrase.recognitionModes), 150 new Locale.Builder().setLanguageTag(aidlPhrase.locale).build(), 151 aidlPhrase.text, 152 Arrays.copyOf(aidlPhrase.users, aidlPhrase.users.length)); 153 } 154 api2aidlRecognitionConfig( SoundTrigger.RecognitionConfig apiConfig)155 public static RecognitionConfig api2aidlRecognitionConfig( 156 SoundTrigger.RecognitionConfig apiConfig) { 157 RecognitionConfig aidlConfig = new RecognitionConfig(); 158 aidlConfig.captureRequested = apiConfig.captureRequested; 159 // apiConfig.allowMultipleTriggers is ignored by the lower layers. 160 aidlConfig.phraseRecognitionExtras = 161 new PhraseRecognitionExtra[apiConfig.keyphrases.length]; 162 for (int i = 0; i < apiConfig.keyphrases.length; ++i) { 163 aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra( 164 apiConfig.keyphrases[i]); 165 } 166 aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length); 167 aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities); 168 return aidlConfig; 169 } 170 aidl2apiRecognitionConfig( RecognitionConfig aidlConfig)171 public static SoundTrigger.RecognitionConfig aidl2apiRecognitionConfig( 172 RecognitionConfig aidlConfig) { 173 var keyphrases = 174 new SoundTrigger.KeyphraseRecognitionExtra[aidlConfig.phraseRecognitionExtras.length]; 175 int i = 0; 176 for (var extras : aidlConfig.phraseRecognitionExtras) { 177 keyphrases[i++] = aidl2apiPhraseRecognitionExtra(extras); 178 } 179 return new SoundTrigger.RecognitionConfig(aidlConfig.captureRequested, 180 false /** allowMultipleTriggers **/, 181 keyphrases, 182 Arrays.copyOf(aidlConfig.data, aidlConfig.data.length), 183 aidl2apiAudioCapabilities(aidlConfig.audioCapabilities)); 184 } 185 api2aidlPhraseRecognitionExtra( SoundTrigger.KeyphraseRecognitionExtra apiExtra)186 public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra( 187 SoundTrigger.KeyphraseRecognitionExtra apiExtra) { 188 PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra(); 189 aidlExtra.id = apiExtra.id; 190 aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes); 191 aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel; 192 aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length]; 193 for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) { 194 aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]); 195 } 196 return aidlExtra; 197 } 198 aidl2apiPhraseRecognitionExtra( PhraseRecognitionExtra aidlExtra)199 public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra( 200 PhraseRecognitionExtra aidlExtra) { 201 SoundTrigger.ConfidenceLevel[] apiLevels = 202 new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length]; 203 for (int i = 0; i < aidlExtra.levels.length; ++i) { 204 apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]); 205 } 206 return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id, 207 aidl2apiRecognitionModes(aidlExtra.recognitionModes), 208 aidlExtra.confidenceLevel, apiLevels); 209 } 210 api2aidlConfidenceLevel( SoundTrigger.ConfidenceLevel apiLevel)211 public static ConfidenceLevel api2aidlConfidenceLevel( 212 SoundTrigger.ConfidenceLevel apiLevel) { 213 ConfidenceLevel aidlLevel = new ConfidenceLevel(); 214 aidlLevel.levelPercent = apiLevel.confidenceLevel; 215 aidlLevel.userId = apiLevel.userId; 216 return aidlLevel; 217 } 218 aidl2apiConfidenceLevel( ConfidenceLevel apiLevel)219 public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel( 220 ConfidenceLevel apiLevel) { 221 return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent); 222 } 223 aidl2apiRecognitionEvent(int modelHandle, int captureSession, RecognitionEventSys aidlEvent)224 public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(int modelHandle, 225 int captureSession, RecognitionEventSys aidlEvent) { 226 RecognitionEvent recognitionEvent = aidlEvent.recognitionEvent; 227 // The API recognition event doesn't allow for a null audio format, even though it doesn't 228 // always make sense. We thus replace it with a default. 229 AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(recognitionEvent.audioConfig, 230 true /*isInput*/); 231 return new SoundTrigger.GenericRecognitionEvent(recognitionEvent.status, modelHandle, 232 recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs, 233 recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat, 234 recognitionEvent.data, 235 recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis, 236 aidlEvent.token); 237 } 238 aidl2apiPhraseRecognitionEvent( int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent)239 public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent( 240 int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent) { 241 PhraseRecognitionEvent recognitionEvent = aidlEvent.phraseRecognitionEvent; 242 SoundTrigger.KeyphraseRecognitionExtra[] apiExtras = 243 new SoundTrigger.KeyphraseRecognitionExtra[recognitionEvent.phraseExtras.length]; 244 for (int i = 0; i < recognitionEvent.phraseExtras.length; ++i) { 245 apiExtras[i] = aidl2apiPhraseRecognitionExtra(recognitionEvent.phraseExtras[i]); 246 } 247 // The API recognition event doesn't allow for a null audio format, even though it doesn't 248 // always make sense. We thus replace it with a default. 249 AudioFormat audioFormat = aidl2apiAudioFormatWithDefault( 250 recognitionEvent.common.audioConfig, 251 true /*isInput*/); 252 return new SoundTrigger.KeyphraseRecognitionEvent(recognitionEvent.common.status, 253 modelHandle, 254 recognitionEvent.common.captureAvailable, captureSession, 255 recognitionEvent.common.captureDelayMs, 256 recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData, 257 audioFormat, 258 recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis, 259 aidlEvent.token); 260 } 261 262 // In case of a null input returns a non-null valid output. aidl2apiAudioFormatWithDefault( @ullable AudioConfig audioConfig, boolean isInput)263 public static AudioFormat aidl2apiAudioFormatWithDefault( 264 @Nullable AudioConfig audioConfig, boolean isInput) { 265 if (audioConfig != null) { 266 return AidlConversion.aidl2api_AudioConfig_AudioFormat(audioConfig, isInput); 267 } 268 return new AudioFormat.Builder() 269 .setSampleRate(48000) 270 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 271 .setChannelMask(AudioFormat.CHANNEL_IN_MONO) 272 .build(); 273 } 274 api2aidlModelParameter(int apiParam)275 public static int api2aidlModelParameter(int apiParam) { 276 switch (apiParam) { 277 case ModelParams.THRESHOLD_FACTOR: 278 return android.media.soundtrigger.ModelParameter.THRESHOLD_FACTOR; 279 default: 280 return android.media.soundtrigger.ModelParameter.INVALID; 281 } 282 } 283 aidl2apiModelParameterRange( @ullable ModelParameterRange aidlRange)284 public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange( 285 @Nullable ModelParameterRange aidlRange) { 286 if (aidlRange == null) { 287 return null; 288 } 289 return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive); 290 } 291 aidl2apiAudioCapabilities(int aidlCapabilities)292 public static int aidl2apiAudioCapabilities(int aidlCapabilities) { 293 int result = 0; 294 if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) { 295 result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; 296 } 297 if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) { 298 result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; 299 } 300 return result; 301 } 302 api2aidlAudioCapabilities(int apiCapabilities)303 public static int api2aidlAudioCapabilities(int apiCapabilities) { 304 int result = 0; 305 if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION) 306 != 0) { 307 result |= AudioCapabilities.ECHO_CANCELLATION; 308 } 309 if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION) 310 != 0) { 311 result |= AudioCapabilities.NOISE_SUPPRESSION; 312 } 313 return result; 314 } 315 byteArrayToSharedMemory(byte[] data, String name)316 public static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) { 317 if (data.length == 0) { 318 return null; 319 } 320 321 try { 322 SharedMemory shmem = SharedMemory.create(name != null ? name : "", data.length); 323 ByteBuffer buffer = shmem.mapReadWrite(); 324 buffer.put(data); 325 shmem.unmap(buffer); 326 ParcelFileDescriptor fd = shmem.getFdDup(); 327 shmem.close(); 328 return fd; 329 } catch (Exception e) { 330 throw new RuntimeException(e); 331 } 332 } 333 sharedMemoryToByteArray(@ullable ParcelFileDescriptor pfd, int size)334 public static byte[] sharedMemoryToByteArray(@Nullable ParcelFileDescriptor pfd, int size) { 335 if (pfd == null || size == 0) { 336 return new byte[0]; 337 } 338 try (SharedMemory mem = SharedMemory.fromFileDescriptor(pfd)) { 339 ByteBuffer buffer = mem.mapReadOnly(); 340 byte[] data = new byte[(size > mem.getSize()) ? mem.getSize() : size]; 341 buffer.get(data); 342 mem.unmap(buffer); 343 return data; 344 } catch (ErrnoException e) { 345 throw new RuntimeException(e); 346 } 347 } 348 } 349