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