diff options
author | Ytai Ben-Tsvi <ytai@google.com> | 2021-02-19 16:07:27 -0800 |
---|---|---|
committer | Ytai Ben-Tsvi <ytai@google.com> | 2021-03-11 14:09:20 -0800 |
commit | d0f8b44f97f8f408796b2599adb1df6c9053298f (patch) | |
tree | 2951b0d7d4c0ff9909f998f9aecacb79b2148f66 /soundtrigger | |
parent | f63888a7f9a11fe98475ce088c504a3bbd75d2e1 (diff) |
Add sthal_cli
This is a simple console app that overrides the default STHAL with a
mock one, useful for manual testing.
This is not production code and it is not designed to be rigorous and/
or complete, but rather as a quick tool that can be easily changed to
need.
Test: Manual running and verification of basic functionality
Change-Id: Ibd13a9dd83c163e02e76ce93f28d036d843854d5
Diffstat (limited to 'soundtrigger')
-rw-r--r-- | soundtrigger/sthal_cli/Android.bp | 8 | ||||
-rw-r--r-- | soundtrigger/sthal_cli/OWNERS | 1 | ||||
-rw-r--r-- | soundtrigger/sthal_cli/java/android/hardware/soundtrigger/cli/SthalCli.java | 406 | ||||
-rw-r--r-- | soundtrigger/sthal_cli/sthal_cli | 7 |
4 files changed, 422 insertions, 0 deletions
diff --git a/soundtrigger/sthal_cli/Android.bp b/soundtrigger/sthal_cli/Android.bp new file mode 100644 index 0000000000..dafdfc3962 --- /dev/null +++ b/soundtrigger/sthal_cli/Android.bp @@ -0,0 +1,8 @@ +java_binary { + name: "sthal_cli", + wrapper: "sthal_cli", + srcs: ["java/**/*.java"], + static_libs: [ + "android.hardware.soundtrigger-V2.4-java", + ], +} diff --git a/soundtrigger/sthal_cli/OWNERS b/soundtrigger/sthal_cli/OWNERS new file mode 100644 index 0000000000..e21b66ecb3 --- /dev/null +++ b/soundtrigger/sthal_cli/OWNERS @@ -0,0 +1 @@ +include /media/java/android/media/soundtrigger_middleware/OWNERS diff --git a/soundtrigger/sthal_cli/java/android/hardware/soundtrigger/cli/SthalCli.java b/soundtrigger/sthal_cli/java/android/hardware/soundtrigger/cli/SthalCli.java new file mode 100644 index 0000000000..ebefd90187 --- /dev/null +++ b/soundtrigger/sthal_cli/java/android/hardware/soundtrigger/cli/SthalCli.java @@ -0,0 +1,406 @@ +/* + * Copyright 2021 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.cli; + +import android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra; +import android.hardware.soundtrigger.V2_0.RecognitionMode; +import android.hardware.soundtrigger.V2_0.SoundModelType; +import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange; +import android.hardware.soundtrigger.V2_4.ISoundTriggerHw; +import android.hardware.soundtrigger.V2_4.ISoundTriggerHwCallback; +import android.hardware.soundtrigger.V2_4.ISoundTriggerHwGlobalCallback; +import android.os.HidlMemoryUtil; +import android.os.HwBinder; +import android.os.RemoteException; +import android.os.SystemProperties; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * This is a quick-and-dirty sound trigger HAL console mock. + * + * It would only work on userdebug builds. + * + * When this app is started, it will initially: + * - Register a ISoundTriggerHw HAL with an instance name "mock". + * - Set a sysprop that tells SoundTriggerMiddlewareService to try to connect to the mock instance + * rather than the default one. + * - Reboot the real (default) HAL. + * + * In response to that, SoundTriggerMiddlewareService is going to connect to the mock HAL and resume + * normal operation. + * + * Our mock HAL will print to stdout every call it receives as well as expose a basic set of + * operations for sending event callbacks to the client. This allows us to simulate the frameworks + * behavior in response to different HAL behaviors. + */ +public class SthalCli { + private static SoundTriggerImpl mService; + private static final Scanner scanner = new Scanner(System.in); + + public static void main(String[] args) { + try { + System.out.println("Registering mock STHAL"); + HwBinder.setTrebleTestingOverride(true); + mService = new SoundTriggerImpl(); + mService.registerAsService("mock"); + + System.out.println("Rebooting STHAL"); + SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", "true"); + SystemProperties.set("sys.audio.restart.hal", "1"); + + while (processCommand()) + ; + } catch (Exception e) { + e.printStackTrace(); + } finally { + cleanup(); + } + } + + private static void cleanup() { + System.out.println("Cleaning up."); + SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", "false"); + HwBinder.setTrebleTestingOverride(false); + } + + private static boolean processCommand() { + String line = scanner.nextLine(); + String[] tokens = line.split("\\s+"); + if (tokens.length < 1) { + return false; + } + switch (tokens[0]) { + case "q": + return false; + + case "a": + mService.sendOnResourcesAvailable(); + return true; + + case "u": + mService.sendModelUnloaded(Integer.parseInt(tokens[1])); + return true; + + case "r": + mService.sendRecognitionEvent( + Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2])); + return true; + + case "p": + mService.sendPhraseRecognitionEvent( + Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2])); + return true; + + case "d": + mService.dumpModels(); + return true; + + case "h": + System.out.print("Available commands:\n" + + "h - help\n" + + "q - quit\n" + + "a - send onResourcesAvailable event\n" + + "u <model> - send modelUnloaded event\n" + + "r <model> <status> - send recognitionEvent\n" + + "p <model> <status> - send phraseRecognitionEvent\n" + + "d - dump models\n"); + + default: + return true; + } + } + + private static class SoundTriggerImpl extends ISoundTriggerHw.Stub { + static class Model { + final ISoundTriggerHwCallback callback; + final SoundModel model; + final PhraseSoundModel phraseModel; + public android.hardware.soundtrigger.V2_3.RecognitionConfig config = null; + + Model(ISoundTriggerHwCallback callback, SoundModel model) { + this.callback = callback; + this.model = model; + this.phraseModel = null; + } + + Model(ISoundTriggerHwCallback callback, PhraseSoundModel model) { + this.callback = callback; + this.model = null; + this.phraseModel = model; + } + } + + private ISoundTriggerHwGlobalCallback mGlobalCallback; + private final ConcurrentMap<Integer, Model> mLoadedModels = new ConcurrentHashMap<>(); + private int mHandleCounter = 1; + + public void dumpModels() { + mLoadedModels.forEach((handle, model) -> { + System.out.println("+++ Model " + handle); + System.out.println(" config = " + model.config); + android.hardware.soundtrigger.V2_3.RecognitionConfig recognitionConfig = + model.config; + if (recognitionConfig != null) { + System.out.println(" ACTIVE recognitionConfig = " + recognitionConfig); + } else { + System.out.println(" INACTIVE"); + } + }); + } + + public void sendOnResourcesAvailable() { + if (mGlobalCallback != null) { + try { + mGlobalCallback.onResourcesAvailable(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + public void sendRecognitionEvent(int modelHandle, int status) { + Model model = mLoadedModels.get(modelHandle); + if (model != null && model.config != null) { + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event = + new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback + .RecognitionEvent(); + event.header.model = modelHandle; + event.header.type = SoundModelType.GENERIC; + event.header.status = status; + event.header.captureSession = model.config.base.header.captureHandle; + event.header.captureAvailable = true; + event.header.audioConfig.channelMask = 16; + event.header.audioConfig.format = 1; + event.header.audioConfig.sampleRateHz = 16000; + event.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]); + try { + model.callback.recognitionCallback_2_1(event, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + model.config = null; + } + } + + public void sendPhraseRecognitionEvent(int modelHandle, int status) { + Model model = mLoadedModels.get(modelHandle); + if (model != null && model.config != null) { + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback + .PhraseRecognitionEvent event = + new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback + .PhraseRecognitionEvent(); + event.common.header.model = modelHandle; + event.common.header.type = SoundModelType.KEYPHRASE; + event.common.header.status = status; + event.common.header.captureSession = model.config.base.header.captureHandle; + event.common.header.captureAvailable = true; + event.common.header.audioConfig.channelMask = 16; + event.common.header.audioConfig.format = 1; + event.common.header.audioConfig.sampleRateHz = 16000; + event.common.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]); + if (!model.phraseModel.phrases.isEmpty()) { + PhraseRecognitionExtra extra = new PhraseRecognitionExtra(); + extra.id = model.phraseModel.phrases.get(0).id; + extra.confidenceLevel = 100; + extra.recognitionModes = model.phraseModel.phrases.get(0).recognitionModes; + event.phraseExtras.add(extra); + } + try { + model.callback.phraseRecognitionCallback_2_1(event, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + model.config = null; + } + } + + public void sendModelUnloaded(int modelHandle) { + Model model = mLoadedModels.remove(modelHandle); + if (model != null) { + try { + model.callback.modelUnloaded(modelHandle); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + @Override + public void registerGlobalCallback(ISoundTriggerHwGlobalCallback callback) { + System.out.println("registerGlobalCallback()"); + mGlobalCallback = callback; + } + + @Override + public void loadSoundModel_2_4(SoundModel soundModel, ISoundTriggerHwCallback callback, + loadSoundModel_2_4Callback _hidl_cb) { + int handle = mHandleCounter++; + System.out.println( + String.format("loadSoundModel_2_4(soundModel=%s) -> %d", soundModel, handle)); + mLoadedModels.put(handle, new Model(callback, soundModel)); + _hidl_cb.onValues(0, handle); + } + + @Override + public void loadPhraseSoundModel_2_4(PhraseSoundModel soundModel, + ISoundTriggerHwCallback callback, loadPhraseSoundModel_2_4Callback _hidl_cb) { + int handle = mHandleCounter++; + System.out.println(String.format( + "loadPhraseSoundModel_2_4(soundModel=%s) -> %d", soundModel, handle)); + mLoadedModels.put(handle, new Model(callback, soundModel)); + _hidl_cb.onValues(0, handle); + } + + @Override + public int startRecognition_2_4( + int modelHandle, android.hardware.soundtrigger.V2_3.RecognitionConfig config) { + System.out.println(String.format("startRecognition_2_4(modelHandle=%d)", modelHandle)); + Model model = mLoadedModels.get(modelHandle); + if (model != null) { + model.config = config; + } + return 0; + } + + @Override + public void getProperties_2_3(getProperties_2_3Callback _hidl_cb) { + System.out.println("getProperties_2_3()"); + android.hardware.soundtrigger.V2_3.Properties properties = + new android.hardware.soundtrigger.V2_3.Properties(); + properties.base.implementor = "Android"; + properties.base.description = "Mock STHAL"; + properties.base.maxSoundModels = 2; + properties.base.maxKeyPhrases = 1; + properties.base.recognitionModes = + RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER; + _hidl_cb.onValues(0, properties); + } + + @Override + public void queryParameter( + int modelHandle, int modelParam, queryParameterCallback _hidl_cb) { + _hidl_cb.onValues(0, new OptionalModelParameterRange()); + } + + @Override + public int getModelState(int modelHandle) { + System.out.println(String.format("getModelState(modelHandle=%d)", modelHandle)); + return 0; + } + + @Override + public int unloadSoundModel(int modelHandle) { + System.out.println(String.format("unloadSoundModel(modelHandle=%d)", modelHandle)); + return 0; + } + + @Override + public int stopRecognition(int modelHandle) { + System.out.println(String.format("stopRecognition(modelHandle=%d)", modelHandle)); + Model model = mLoadedModels.get(modelHandle); + if (model != null) { + model.config = null; + } + return 0; + } + + @Override + public void debug(android.os.NativeHandle fd, java.util.ArrayList<String> options) { + if (!options.isEmpty()) { + switch (options.get(0)) { + case "reboot": + System.out.println("Received a reboot request. Exiting."); + cleanup(); + System.exit(1); + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // Everything below is not implemented and not expected to be called. + + @Override + public int startRecognition_2_3( + int modelHandle, android.hardware.soundtrigger.V2_3.RecognitionConfig config) { + throw new UnsupportedOperationException(); + } + + @Override + public int setParameter(int modelHandle, int modelParam, int value) { + throw new UnsupportedOperationException(); + } + + @Override + public void getParameter(int modelHandle, int modelParam, getParameterCallback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadSoundModel_2_1(SoundModel soundModel, + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, + loadSoundModel_2_1Callback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadPhraseSoundModel_2_1(PhraseSoundModel soundModel, + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, + loadPhraseSoundModel_2_1Callback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public int startRecognition_2_1(int modelHandle, RecognitionConfig config, + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie) { + throw new UnsupportedOperationException(); + } + + @Override + public void getProperties(getPropertiesCallback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadSoundModel( + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel soundModel, + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, + loadSoundModelCallback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public void loadPhraseSoundModel( + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel soundModel, + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, + loadPhraseSoundModelCallback _hidl_cb) { + throw new UnsupportedOperationException(); + } + + @Override + public int startRecognition(int modelHandle, + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie) { + throw new UnsupportedOperationException(); + } + + @Override + public int stopAllRecognitions() { + throw new UnsupportedOperationException(); + } + } +}
\ No newline at end of file diff --git a/soundtrigger/sthal_cli/sthal_cli b/soundtrigger/sthal_cli/sthal_cli new file mode 100644 index 0000000000..9fc6779bf7 --- /dev/null +++ b/soundtrigger/sthal_cli/sthal_cli @@ -0,0 +1,7 @@ +#!/system/bin/sh +# Script to start "sthal_cli" on the device +# +base=/system +export CLASSPATH=$base/framework/sthal_cli.jar +exec app_process $base/bin android.hardware.soundtrigger.cli.SthalCli "$@" + |