349 lines
15 KiB
Java
349 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.hardware.soundtrigger;
|
|
|
|
import android.annotation.Nullable;
|
|
import android.media.AudioFormat;
|
|
import android.media.audio.common.AidlConversion;
|
|
import android.media.audio.common.AudioConfig;
|
|
import android.media.soundtrigger.AudioCapabilities;
|
|
import android.media.soundtrigger.ConfidenceLevel;
|
|
import android.media.soundtrigger.ModelParameterRange;
|
|
import android.media.soundtrigger.Phrase;
|
|
import android.media.soundtrigger.PhraseRecognitionEvent;
|
|
import android.media.soundtrigger.PhraseRecognitionExtra;
|
|
import android.media.soundtrigger.PhraseSoundModel;
|
|
import android.media.soundtrigger.Properties;
|
|
import android.media.soundtrigger.RecognitionConfig;
|
|
import android.media.soundtrigger.RecognitionEvent;
|
|
import android.media.soundtrigger.RecognitionMode;
|
|
import android.media.soundtrigger.SoundModel;
|
|
import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
|
|
import android.media.soundtrigger_middleware.RecognitionEventSys;
|
|
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.SharedMemory;
|
|
import android.system.ErrnoException;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
import java.util.Locale;
|
|
import java.util.UUID;
|
|
|
|
/** @hide */
|
|
public class ConversionUtil {
|
|
public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
|
|
SoundTriggerModuleDescriptor aidlDesc) {
|
|
Properties properties = aidlDesc.properties;
|
|
return new SoundTrigger.ModuleProperties(
|
|
aidlDesc.handle,
|
|
properties.implementor,
|
|
properties.description,
|
|
properties.uuid,
|
|
properties.version,
|
|
properties.supportedModelArch,
|
|
properties.maxSoundModels,
|
|
properties.maxKeyPhrases,
|
|
properties.maxUsers,
|
|
aidl2apiRecognitionModes(properties.recognitionModes),
|
|
properties.captureTransition,
|
|
properties.maxBufferMs,
|
|
properties.concurrentCapture,
|
|
properties.powerConsumptionMw,
|
|
properties.triggerInEvent,
|
|
aidl2apiAudioCapabilities(properties.audioCapabilities)
|
|
);
|
|
}
|
|
|
|
public static int aidl2apiRecognitionModes(int aidlModes) {
|
|
int result = 0;
|
|
if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
|
|
result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
|
|
}
|
|
if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
|
|
result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
|
|
}
|
|
if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
|
|
result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
|
|
}
|
|
if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
|
|
result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static int api2aidlRecognitionModes(int apiModes) {
|
|
int result = 0;
|
|
if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
|
|
result |= RecognitionMode.VOICE_TRIGGER;
|
|
}
|
|
if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
|
|
result |= RecognitionMode.USER_IDENTIFICATION;
|
|
}
|
|
if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
|
|
result |= RecognitionMode.USER_AUTHENTICATION;
|
|
}
|
|
if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
|
|
result |= RecognitionMode.GENERIC_TRIGGER;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
|
|
return api2aidlSoundModel(apiModel);
|
|
}
|
|
|
|
public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
|
|
SoundModel aidlModel = new SoundModel();
|
|
aidlModel.type = apiModel.getType();
|
|
aidlModel.uuid = api2aidlUuid(apiModel.getUuid());
|
|
aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid());
|
|
byte[] data = apiModel.getData();
|
|
aidlModel.data = byteArrayToSharedMemory(data, "SoundTrigger SoundModel");
|
|
aidlModel.dataSize = data.length;
|
|
return aidlModel;
|
|
}
|
|
|
|
public static String api2aidlUuid(UUID apiUuid) {
|
|
return apiUuid.toString();
|
|
}
|
|
|
|
public static PhraseSoundModel api2aidlPhraseSoundModel(
|
|
SoundTrigger.KeyphraseSoundModel apiModel) {
|
|
PhraseSoundModel aidlModel = new PhraseSoundModel();
|
|
aidlModel.common = api2aidlSoundModel(apiModel);
|
|
aidlModel.phrases = new Phrase[apiModel.getKeyphrases().length];
|
|
for (int i = 0; i < apiModel.getKeyphrases().length; ++i) {
|
|
aidlModel.phrases[i] = api2aidlPhrase(apiModel.getKeyphrases()[i]);
|
|
}
|
|
return aidlModel;
|
|
}
|
|
|
|
public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
|
|
Phrase aidlPhrase = new Phrase();
|
|
aidlPhrase.id = apiPhrase.getId();
|
|
aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.getRecognitionModes());
|
|
aidlPhrase.users = Arrays.copyOf(apiPhrase.getUsers(), apiPhrase.getUsers().length);
|
|
aidlPhrase.locale = apiPhrase.getLocale().toLanguageTag();
|
|
aidlPhrase.text = apiPhrase.getText();
|
|
return aidlPhrase;
|
|
}
|
|
|
|
public static SoundTrigger.Keyphrase aidl2apiPhrase(Phrase aidlPhrase) {
|
|
return new SoundTrigger.Keyphrase(aidlPhrase.id,
|
|
aidl2apiRecognitionModes(aidlPhrase.recognitionModes),
|
|
new Locale.Builder().setLanguageTag(aidlPhrase.locale).build(),
|
|
aidlPhrase.text,
|
|
Arrays.copyOf(aidlPhrase.users, aidlPhrase.users.length));
|
|
}
|
|
|
|
public static RecognitionConfig api2aidlRecognitionConfig(
|
|
SoundTrigger.RecognitionConfig apiConfig) {
|
|
RecognitionConfig aidlConfig = new RecognitionConfig();
|
|
aidlConfig.captureRequested = apiConfig.captureRequested;
|
|
// apiConfig.allowMultipleTriggers is ignored by the lower layers.
|
|
aidlConfig.phraseRecognitionExtras =
|
|
new PhraseRecognitionExtra[apiConfig.keyphrases.length];
|
|
for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
|
|
aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
|
|
apiConfig.keyphrases[i]);
|
|
}
|
|
aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
|
|
aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
|
|
return aidlConfig;
|
|
}
|
|
|
|
public static SoundTrigger.RecognitionConfig aidl2apiRecognitionConfig(
|
|
RecognitionConfig aidlConfig) {
|
|
var keyphrases =
|
|
new SoundTrigger.KeyphraseRecognitionExtra[aidlConfig.phraseRecognitionExtras.length];
|
|
int i = 0;
|
|
for (var extras : aidlConfig.phraseRecognitionExtras) {
|
|
keyphrases[i++] = aidl2apiPhraseRecognitionExtra(extras);
|
|
}
|
|
return new SoundTrigger.RecognitionConfig(aidlConfig.captureRequested,
|
|
false /** allowMultipleTriggers **/,
|
|
keyphrases,
|
|
Arrays.copyOf(aidlConfig.data, aidlConfig.data.length),
|
|
aidl2apiAudioCapabilities(aidlConfig.audioCapabilities));
|
|
}
|
|
|
|
public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
|
|
SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
|
|
PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
|
|
aidlExtra.id = apiExtra.id;
|
|
aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
|
|
aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
|
|
aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
|
|
for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
|
|
aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
|
|
}
|
|
return aidlExtra;
|
|
}
|
|
|
|
public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
|
|
PhraseRecognitionExtra aidlExtra) {
|
|
SoundTrigger.ConfidenceLevel[] apiLevels =
|
|
new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
|
|
for (int i = 0; i < aidlExtra.levels.length; ++i) {
|
|
apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
|
|
}
|
|
return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
|
|
aidl2apiRecognitionModes(aidlExtra.recognitionModes),
|
|
aidlExtra.confidenceLevel, apiLevels);
|
|
}
|
|
|
|
public static ConfidenceLevel api2aidlConfidenceLevel(
|
|
SoundTrigger.ConfidenceLevel apiLevel) {
|
|
ConfidenceLevel aidlLevel = new ConfidenceLevel();
|
|
aidlLevel.levelPercent = apiLevel.confidenceLevel;
|
|
aidlLevel.userId = apiLevel.userId;
|
|
return aidlLevel;
|
|
}
|
|
|
|
public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
|
|
ConfidenceLevel apiLevel) {
|
|
return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
|
|
}
|
|
|
|
public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(int modelHandle,
|
|
int captureSession, RecognitionEventSys aidlEvent) {
|
|
RecognitionEvent recognitionEvent = aidlEvent.recognitionEvent;
|
|
// The API recognition event doesn't allow for a null audio format, even though it doesn't
|
|
// always make sense. We thus replace it with a default.
|
|
AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(recognitionEvent.audioConfig,
|
|
true /*isInput*/);
|
|
return new SoundTrigger.GenericRecognitionEvent(recognitionEvent.status, modelHandle,
|
|
recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs,
|
|
recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat,
|
|
recognitionEvent.data,
|
|
recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis,
|
|
aidlEvent.token);
|
|
}
|
|
|
|
public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
|
|
int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent) {
|
|
PhraseRecognitionEvent recognitionEvent = aidlEvent.phraseRecognitionEvent;
|
|
SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
|
|
new SoundTrigger.KeyphraseRecognitionExtra[recognitionEvent.phraseExtras.length];
|
|
for (int i = 0; i < recognitionEvent.phraseExtras.length; ++i) {
|
|
apiExtras[i] = aidl2apiPhraseRecognitionExtra(recognitionEvent.phraseExtras[i]);
|
|
}
|
|
// The API recognition event doesn't allow for a null audio format, even though it doesn't
|
|
// always make sense. We thus replace it with a default.
|
|
AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(
|
|
recognitionEvent.common.audioConfig,
|
|
true /*isInput*/);
|
|
return new SoundTrigger.KeyphraseRecognitionEvent(recognitionEvent.common.status,
|
|
modelHandle,
|
|
recognitionEvent.common.captureAvailable, captureSession,
|
|
recognitionEvent.common.captureDelayMs,
|
|
recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData,
|
|
audioFormat,
|
|
recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis,
|
|
aidlEvent.token);
|
|
}
|
|
|
|
// In case of a null input returns a non-null valid output.
|
|
public static AudioFormat aidl2apiAudioFormatWithDefault(
|
|
@Nullable AudioConfig audioConfig, boolean isInput) {
|
|
if (audioConfig != null) {
|
|
return AidlConversion.aidl2api_AudioConfig_AudioFormat(audioConfig, isInput);
|
|
}
|
|
return new AudioFormat.Builder()
|
|
.setSampleRate(48000)
|
|
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
|
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
|
|
.build();
|
|
}
|
|
|
|
public static int api2aidlModelParameter(int apiParam) {
|
|
switch (apiParam) {
|
|
case ModelParams.THRESHOLD_FACTOR:
|
|
return android.media.soundtrigger.ModelParameter.THRESHOLD_FACTOR;
|
|
default:
|
|
return android.media.soundtrigger.ModelParameter.INVALID;
|
|
}
|
|
}
|
|
|
|
public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
|
|
@Nullable ModelParameterRange aidlRange) {
|
|
if (aidlRange == null) {
|
|
return null;
|
|
}
|
|
return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
|
|
}
|
|
|
|
public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
|
|
int result = 0;
|
|
if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
|
|
result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION;
|
|
}
|
|
if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
|
|
result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static int api2aidlAudioCapabilities(int apiCapabilities) {
|
|
int result = 0;
|
|
if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION)
|
|
!= 0) {
|
|
result |= AudioCapabilities.ECHO_CANCELLATION;
|
|
}
|
|
if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION)
|
|
!= 0) {
|
|
result |= AudioCapabilities.NOISE_SUPPRESSION;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
|
|
if (data.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
SharedMemory shmem = SharedMemory.create(name != null ? name : "", data.length);
|
|
ByteBuffer buffer = shmem.mapReadWrite();
|
|
buffer.put(data);
|
|
shmem.unmap(buffer);
|
|
ParcelFileDescriptor fd = shmem.getFdDup();
|
|
shmem.close();
|
|
return fd;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public static byte[] sharedMemoryToByteArray(@Nullable ParcelFileDescriptor pfd, int size) {
|
|
if (pfd == null || size == 0) {
|
|
return new byte[0];
|
|
}
|
|
try (SharedMemory mem = SharedMemory.fromFileDescriptor(pfd)) {
|
|
ByteBuffer buffer = mem.mapReadOnly();
|
|
byte[] data = new byte[(size > mem.getSize()) ? mem.getSize() : size];
|
|
buffer.get(data);
|
|
mem.unmap(buffer);
|
|
return data;
|
|
} catch (ErrnoException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|