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