blob: 9f211db5fdc5bec5c9c2e9ddd60461eeb1ec8e1a [file] [log] [blame]
/*
* 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 com.android.server.soundtrigger_middleware;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.audio.common.V2_0.Uuid;
import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
import android.media.audio.common.AidlConversion;
import android.media.audio.common.AudioConfig;
import android.media.audio.common.AudioConfigBase;
import android.media.audio.common.AudioOffloadInfo;
import android.media.soundtrigger.AudioCapabilities;
import android.media.soundtrigger.ConfidenceLevel;
import android.media.soundtrigger.ModelParameter;
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.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.SoundModelType;
import android.os.HidlMemory;
import android.os.HidlMemoryUtil;
import android.os.ParcelFileDescriptor;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.regex.Matcher;
/**
* Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
* types.
*
* @hide
*/
class ConversionUtil {
static @NonNull
Properties hidl2aidlProperties(
@NonNull ISoundTriggerHw.Properties hidlProperties) {
Properties aidlProperties = new Properties();
aidlProperties.implementor = hidlProperties.implementor;
aidlProperties.description = hidlProperties.description;
aidlProperties.version = hidlProperties.version;
aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
aidlProperties.maxUsers = hidlProperties.maxUsers;
aidlProperties.recognitionModes =
hidl2aidlRecognitionModes(hidlProperties.recognitionModes);
aidlProperties.captureTransition = hidlProperties.captureTransition;
aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
return aidlProperties;
}
static @NonNull Properties hidl2aidlProperties(
@NonNull android.hardware.soundtrigger.V2_3.Properties hidlProperties) {
Properties aidlProperties = hidl2aidlProperties(hidlProperties.base);
aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
aidlProperties.audioCapabilities =
hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
return aidlProperties;
}
static @NonNull
String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
if (hidlUuid.node == null || hidlUuid.node.length != 6) {
throw new IllegalArgumentException("UUID.node must be of length 6.");
}
return String.format(UuidUtil.FORMAT,
hidlUuid.timeLow,
hidlUuid.timeMid,
hidlUuid.versionAndTimeHigh,
hidlUuid.variantAndClockSeqHigh,
hidlUuid.node[0],
hidlUuid.node[1],
hidlUuid.node[2],
hidlUuid.node[3],
hidlUuid.node[4],
hidlUuid.node[5]);
}
static @NonNull
Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
if (!matcher.matches()) {
throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
}
Uuid hidlUuid = new Uuid();
hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
(byte) Integer.parseUnsignedInt(matcher.group(6), 16),
(byte) Integer.parseUnsignedInt(matcher.group(7), 16),
(byte) Integer.parseUnsignedInt(matcher.group(8), 16),
(byte) Integer.parseUnsignedInt(matcher.group(9), 16),
(byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
return hidlUuid;
}
static int aidl2hidlSoundModelType(int aidlType) {
switch (aidlType) {
case SoundModelType.GENERIC:
return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
case SoundModelType.KEYPHRASE:
return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
default:
throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
}
}
static int hidl2aidlSoundModelType(int hidlType) {
switch (hidlType) {
case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
return SoundModelType.GENERIC;
case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
return SoundModelType.KEYPHRASE;
default:
throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
}
}
static @NonNull
ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
hidlPhrase.id = aidlPhrase.id;
hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
for (int aidlUser : aidlPhrase.users) {
hidlPhrase.users.add(aidlUser);
}
hidlPhrase.locale = aidlPhrase.locale;
hidlPhrase.text = aidlPhrase.text;
return hidlPhrase;
}
static int aidl2hidlRecognitionModes(int aidlModes) {
int hidlModes = 0;
if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
}
if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
}
if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
}
if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
}
return hidlModes;
}
static int hidl2aidlRecognitionModes(int hidlModes) {
int aidlModes = 0;
if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
aidlModes |= RecognitionMode.VOICE_TRIGGER;
}
if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
!= 0) {
aidlModes |= RecognitionMode.USER_IDENTIFICATION;
}
if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
!= 0) {
aidlModes |= RecognitionMode.USER_AUTHENTICATION;
}
if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
aidlModes |= RecognitionMode.GENERIC_TRIGGER;
}
return aidlModes;
}
static @NonNull
ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
hidlModel.data = parcelFileDescriptorToHidlMemory(aidlModel.data, aidlModel.dataSize);
return hidlModel;
}
static @NonNull
ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
@NonNull PhraseSoundModel aidlModel) {
ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
for (Phrase aidlPhrase : aidlModel.phrases) {
hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
}
return hidlModel;
}
static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
@NonNull RecognitionConfig aidlConfig, int deviceHandle, int ioHandle) {
android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
new android.hardware.soundtrigger.V2_3.RecognitionConfig();
hidlConfig.base.header.captureDevice = deviceHandle;
hidlConfig.base.header.captureHandle = ioHandle;
hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
}
hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
"SoundTrigger RecognitionConfig");
hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
return hidlConfig;
}
static @NonNull
android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
@NonNull PhraseRecognitionExtra aidlExtra) {
android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
hidlExtra.id = aidlExtra.id;
hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
}
return hidlExtra;
}
static @NonNull
PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
@NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
aidlExtra.id = hidlExtra.id;
aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
for (int i = 0; i < hidlExtra.levels.size(); ++i) {
aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
}
return aidlExtra;
}
static @NonNull
android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
@NonNull ConfidenceLevel aidlLevel) {
android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
hidlLevel.userId = aidlLevel.userId;
hidlLevel.levelPercent = aidlLevel.levelPercent;
return hidlLevel;
}
static @NonNull
ConfidenceLevel hidl2aidlConfidenceLevel(
@NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
ConfidenceLevel aidlLevel = new ConfidenceLevel();
aidlLevel.userId = hidlLevel.userId;
aidlLevel.levelPercent = hidlLevel.levelPercent;
return aidlLevel;
}
static int hidl2aidlRecognitionStatus(int hidlStatus) {
switch (hidlStatus) {
case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
return RecognitionStatus.SUCCESS;
case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
return RecognitionStatus.ABORTED;
case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
return RecognitionStatus.FAILURE;
case 3: // This doesn't have a constant in HIDL.
return RecognitionStatus.FORCED;
default:
throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
}
}
static @NonNull
RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
RecognitionEvent aidlEvent = new RecognitionEvent();
aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
aidlEvent.captureAvailable = hidlEvent.captureAvailable;
aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
aidlEvent.triggerInData = hidlEvent.triggerInData;
aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig, true /*isInput*/);
aidlEvent.data = new byte[hidlEvent.data.size()];
for (int i = 0; i < aidlEvent.data.length; ++i) {
aidlEvent.data[i] = hidlEvent.data.get(i);
}
aidlEvent.recognitionStillActive = aidlEvent.status == RecognitionStatus.FORCED;
return aidlEvent;
}
static @NonNull
RecognitionEvent hidl2aidlRecognitionEvent(
@NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
// Data needs to get overridden with 2.1 data.
aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
return aidlEvent;
}
static @NonNull
PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
hidlEvent.phraseExtras.get(i));
}
return aidlEvent;
}
static @NonNull
PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
@NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
hidlEvent.phraseExtras.get(i));
}
return aidlEvent;
}
static @NonNull
AudioConfig hidl2aidlAudioConfig(
@NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig, boolean isInput) {
AudioConfig aidlConfig = new AudioConfig();
aidlConfig.base = hidl2aidlAudioConfigBase(hidlConfig.sampleRateHz, hidlConfig.channelMask,
hidlConfig.format, isInput);
aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
aidlConfig.frameCount = hidlConfig.frameCount;
return aidlConfig;
}
static @NonNull
AudioOffloadInfo hidl2aidlOffloadInfo(
@NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
aidlInfo.base = hidl2aidlAudioConfigBase(hidlInfo.sampleRateHz, hidlInfo.channelMask,
hidlInfo.format, false /*isInput*/);
aidlInfo.streamType = AidlConversion.legacy2aidl_audio_stream_type_t_AudioStreamType(
hidlInfo.streamType);
aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
aidlInfo.durationUs = hidlInfo.durationMicroseconds;
aidlInfo.hasVideo = hidlInfo.hasVideo;
aidlInfo.isStreaming = hidlInfo.isStreaming;
aidlInfo.bitWidth = hidlInfo.bitWidth;
aidlInfo.offloadBufferSize = hidlInfo.bufferSize;
aidlInfo.usage = AidlConversion.legacy2aidl_audio_usage_t_AudioUsage(hidlInfo.usage);
return aidlInfo;
}
// Ideally we would want to convert AudioConfigBase as a unit, however
// HIDL V2 lacks this type, so convert by field instead.
static @NonNull
AudioConfigBase hidl2aidlAudioConfigBase(int sampleRateHz, int channelMask, int format,
boolean isInput) {
AudioConfigBase aidlBase = new AudioConfigBase();
aidlBase.sampleRate = sampleRateHz;
// Relies on the fact that HIDL AudioChannelMask uses the same constant values as
// system/audio.h.
aidlBase.channelMask = AidlConversion.legacy2aidl_audio_channel_mask_t_AudioChannelLayout(
channelMask, isInput);
// Relies on the fact that HIDL AudioFormat uses the same constant values as system/audio.h.
aidlBase.format = AidlConversion.legacy2aidl_audio_format_t_AudioFormatDescription(format);
return aidlBase;
}
@Nullable
static ModelParameterRange hidl2aidlModelParameterRange(
android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
if (hidlRange == null) {
return null;
}
ModelParameterRange aidlRange = new ModelParameterRange();
aidlRange.minInclusive = hidlRange.start;
aidlRange.maxInclusive = hidlRange.end;
return aidlRange;
}
static int aidl2hidlModelParameter(int aidlParam) {
switch (aidlParam) {
case ModelParameter.THRESHOLD_FACTOR:
return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
default:
return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
}
}
static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
int aidlCapabilities = 0;
if ((hidlCapabilities
& android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
}
if ((hidlCapabilities
& android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
}
return aidlCapabilities;
}
/**
* Convert an AIDL representation of a shared memory block (ParcelFileDescriptor + size) to the
* HIDL representation (HidlMemory). Will not change the incoming data or any ownership
* semantics, but rather duplicate the underlying FD.
*
* @param data The incoming memory block. May be null if dataSize is 0.
* @param dataSize The number of bytes in the block.
* @return A HidlMemory representation of the memory block. Will be non-null even for an empty
* block.
*/
private static @NonNull
HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) {
if (dataSize > 0) {
return HidlMemoryUtil.fileDescriptorToHidlMemory(data.getFileDescriptor(), dataSize);
} else {
return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0);
}
}
}