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