SoundTriggerHelper.java revision 055897208d659e9734a82def88be4a806ff55448
1/** 2 * Copyright (C) 2014 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 17package com.android.server.voiceinteraction; 18 19import android.hardware.soundtrigger.IRecognitionStatusCallback; 20import android.hardware.soundtrigger.SoundTrigger; 21import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 22import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 23import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 24import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 25import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; 26import android.hardware.soundtrigger.SoundTriggerModule; 27import android.os.RemoteException; 28import android.util.Slog; 29import android.util.SparseArray; 30 31import java.util.ArrayList; 32 33/** 34 * Helper for {@link SoundTrigger} APIs. 35 * Currently this just acts as an abstraction over all SoundTrigger API calls. 36 * 37 * @hide 38 */ 39public class SoundTriggerHelper implements SoundTrigger.StatusListener { 40 static final String TAG = "SoundTriggerHelper"; 41 // TODO: Remove this. 42 static final int TEMP_KEYPHRASE_ID = 1; 43 44 /** 45 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel, 46 * IRecognitionStatusCallback, RecognitionConfig)}, 47 * {@link #stopRecognition(int, IRecognitionStatusCallback)} 48 */ 49 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 50 public static final int STATUS_OK = SoundTrigger.STATUS_OK; 51 52 private static final int INVALID_SOUND_MODEL_HANDLE = -1; 53 54 /** The {@link DspInfo} for the system, or null if none exists. */ 55 final ModuleProperties moduleProperties; 56 57 /** The properties for the DSP module */ 58 private final SoundTriggerModule mModule; 59 60 // Use a RemoteCallbackList here? 61 private final SparseArray<IRecognitionStatusCallback> mActiveListeners; 62 63 private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 64 65 SoundTriggerHelper() { 66 ArrayList <ModuleProperties> modules = new ArrayList<>(); 67 int status = SoundTrigger.listModules(modules); 68 mActiveListeners = new SparseArray<>(1); 69 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { 70 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); 71 moduleProperties = null; 72 mModule = null; 73 } else { 74 // TODO: Figure out how to determine which module corresponds to the DSP hardware. 75 moduleProperties = modules.get(0); 76 mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); 77 } 78 } 79 80 /** 81 * @return True, if a recognition for the given {@link Keyphrase} is active. 82 */ 83 synchronized boolean isKeyphraseActive(Keyphrase keyphrase) { 84 if (keyphrase == null) { 85 Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase"); 86 return false; 87 } 88 return mActiveListeners.get(keyphrase.id) != null; 89 } 90 91 /** 92 * Starts recognition for the given keyphraseId. 93 * 94 * @param keyphraseId The identifier of the keyphrase for which 95 * the recognition is to be started. 96 * @param soundModel The sound model to use for recognition. 97 * @param listener The listener for the recognition events related to the given keyphrase. 98 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 99 */ 100 synchronized int startRecognition(int keyphraseId, 101 KeyphraseSoundModel soundModel, 102 IRecognitionStatusCallback listener, 103 RecognitionConfig recognitionConfig) { 104 if (moduleProperties == null || mModule == null) { 105 Slog.w(TAG, "Attempting startRecognition without the capability"); 106 return STATUS_ERROR; 107 } 108 109 IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId); 110 if (oldListener != null && oldListener != listener) { 111 if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { 112 Slog.w(TAG, "Canceling previous recognition"); 113 // TODO: Inspect the return codes here. 114 mModule.unloadSoundModel(mCurrentSoundModelHandle); 115 } 116 try { 117 mActiveListeners.get(keyphraseId).onDetectionStopped(); 118 } catch (RemoteException e) { 119 Slog.w(TAG, "RemoteException in onDetectionStopped"); 120 } 121 mActiveListeners.remove(keyphraseId); 122 } 123 124 int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE }; 125 int status = mModule.loadSoundModel(soundModel, handle); 126 if (status != SoundTrigger.STATUS_OK) { 127 Slog.w(TAG, "loadSoundModel call failed with " + status); 128 return STATUS_ERROR; 129 } 130 if (handle[0] == INVALID_SOUND_MODEL_HANDLE) { 131 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); 132 return STATUS_ERROR; 133 } 134 135 // Start the recognition. 136 status = mModule.startRecognition(handle[0], recognitionConfig); 137 if (status != SoundTrigger.STATUS_OK) { 138 Slog.w(TAG, "startRecognition failed with " + status); 139 return STATUS_ERROR; 140 } 141 142 // Everything went well! 143 mCurrentSoundModelHandle = handle[0]; 144 // Register the new listener. This replaces the old one. 145 // There can only be a maximum of one active listener for a keyphrase 146 // at any given time. 147 mActiveListeners.put(keyphraseId, listener); 148 return STATUS_OK; 149 } 150 151 /** 152 * Stops recognition for the given {@link Keyphrase} if a recognition is 153 * currently active. 154 * 155 * @param keyphraseId The identifier of the keyphrase for which 156 * the recognition is to be stopped. 157 * @param listener The listener for the recognition events related to the given keyphrase. 158 * 159 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. 160 */ 161 synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 162 if (moduleProperties == null || mModule == null) { 163 Slog.w(TAG, "Attempting stopRecognition without the capability"); 164 return STATUS_ERROR; 165 } 166 167 IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId); 168 if (currentListener == null) { 169 // startRecognition hasn't been called or it failed. 170 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); 171 return STATUS_ERROR; 172 } else if (currentListener != listener) { 173 // TODO: Figure out if this should match the listener that was passed in during 174 // startRecognition, or should we allow a different listener to stop the recognition, 175 // in which case we don't need to pass in a listener here. 176 Slog.w(TAG, "Attempting stopRecognition for another recognition"); 177 return STATUS_ERROR; 178 } else { 179 // Stop recognition if it's the current one, ignore otherwise. 180 // TODO: Inspect the return codes here. 181 int status = mModule.stopRecognition(mCurrentSoundModelHandle); 182 if (status != SoundTrigger.STATUS_OK) { 183 Slog.w(TAG, "stopRecognition call failed with " + status); 184 return STATUS_ERROR; 185 } 186 status = mModule.unloadSoundModel(mCurrentSoundModelHandle); 187 if (status != SoundTrigger.STATUS_OK) { 188 Slog.w(TAG, "unloadSoundModel call failed with " + status); 189 return STATUS_ERROR; 190 } 191 192 mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; 193 mActiveListeners.remove(keyphraseId); 194 return STATUS_OK; 195 } 196 } 197 198 //---- SoundTrigger.StatusListener methods 199 @Override 200 public void onRecognition(RecognitionEvent event) { 201 // Check which keyphrase triggered, and fire the appropriate event. 202 // TODO: Get the keyphrase out of the event and fire events on it. 203 // For now, as a nasty workaround, we fire all events to the listener for 204 // keyphrase with TEMP_KEYPHRASE_ID. 205 IRecognitionStatusCallback listener = null; 206 synchronized(this) { 207 // TODO: The keyphrase should come from the recognition event 208 // as it may be for a different keyphrase than the current one. 209 listener = mActiveListeners.get(TEMP_KEYPHRASE_ID); 210 } 211 if (listener == null) { 212 Slog.w(TAG, "received onRecognition event without any listener for it"); 213 return; 214 } 215 216 switch (event.status) { 217 case SoundTrigger.RECOGNITION_STATUS_SUCCESS: 218 // TODO: Pass the captured audio back. 219 try { 220 listener.onDetected(null); 221 } catch (RemoteException e) { 222 Slog.w(TAG, "RemoteException in onDetected"); 223 } 224 break; 225 case SoundTrigger.RECOGNITION_STATUS_ABORT: 226 try { 227 listener.onDetectionStopped(); 228 } catch (RemoteException e) { 229 Slog.w(TAG, "RemoteException in onDetectionStopped"); 230 } 231 break; 232 case SoundTrigger.RECOGNITION_STATUS_FAILURE: 233 try { 234 listener.onDetectionStopped(); 235 } catch (RemoteException e) { 236 Slog.w(TAG, "RemoteException in onDetectionStopped"); 237 } 238 break; 239 } 240 } 241 242 @Override 243 public void onServiceDied() { 244 // TODO: Figure out how to restart the recognition here. 245 } 246} 247