Loading services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +127 −23 Original line number Original line Diff line number Diff line Loading @@ -46,6 +46,10 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.SoundModel; import android.hardware.soundtrigger.SoundTrigger.SoundModel; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.SoundTriggerDetectionService; import android.media.soundtrigger.SoundTriggerDetectionService; Loading Loading @@ -654,10 +658,47 @@ public class SoundTriggerService extends SystemService { } } } } private interface Operation { /** * A single operation run in a {@link RemoteSoundTriggerDetectionService}. * * <p>Once the remote service is connected either setup + execute or setup + stop is executed. */ private static class Operation { private interface ExecuteOp { void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; } } private final @Nullable Runnable mSetupOp; private final @NonNull ExecuteOp mExecuteOp; private final @Nullable Runnable mDropOp; private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp, @Nullable Runnable cancelOp) { mSetupOp = setupOp; mExecuteOp = executeOp; mDropOp = cancelOp; } private void setup() { if (mSetupOp != null) { mSetupOp.run(); } } void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException { setup(); mExecuteOp.run(opId, service); } void drop() { setup(); if (mDropOp != null) { mDropOp.run(); } } } /** /** * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed * when the service connects. * when the service connects. Loading Loading @@ -902,6 +943,10 @@ public class SoundTriggerService extends SystemService { private void runOrAddOperation(Operation op) { private void runOrAddOperation(Operation op) { synchronized (mRemoteServiceLock) { synchronized (mRemoteServiceLock) { if (mIsDestroyed || mDestroyOnceRunningOpsDone) { if (mIsDestroyed || mDestroyOnceRunningOpsDone) { Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for " + "destruction"); op.drop(); return; return; } } Loading @@ -922,9 +967,15 @@ public class SoundTriggerService extends SystemService { int opsAdded = mNumOps.getOpsAdded(); int opsAdded = mNumOps.getOpsAdded(); if (mNumOps.getOpsAdded() >= opsAllowed) { if (mNumOps.getOpsAdded() >= opsAllowed) { try { if (DEBUG || opsAllowed + 10 > opsAdded) { if (DEBUG || opsAllowed + 10 > opsAdded) { Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were " Slog.w(TAG, mPuuid + ": Dropped operation as too many operations " + "run in last 24 hours"); + "were run in last 24 hours"); } op.drop(); } catch (Exception e) { Slog.e(TAG, mPuuid + ": Could not drop operation", e); } } } else { } else { mNumOps.addOp(currentTime); mNumOps.addOp(currentTime); Loading Loading @@ -972,34 +1023,87 @@ public class SoundTriggerService extends SystemService { + ")"); + ")"); } } /** * Create an AudioRecord enough for starting and releasing the data buffered for the event. * * @param event The event that was received * @return The initialized AudioRecord */ private @NonNull AudioRecord createAudioRecordForEvent( @NonNull SoundTrigger.GenericRecognitionEvent event) { AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD); AudioAttributes attributes = attributesBuilder.build(); // Use same AudioFormat processing as in RecognitionEvent.fromParcel AudioFormat originalFormat = event.getCaptureFormat(); AudioFormat captureFormat = (new AudioFormat.Builder()) .setChannelMask(originalFormat.getChannelMask()) .setEncoding(originalFormat.getEncoding()) .setSampleRate(originalFormat.getSampleRate()) .build(); int bufferSize = AudioRecord.getMinBufferSize( captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED ? AudioFormat.SAMPLE_RATE_HZ_MAX : captureFormat.getSampleRate(), captureFormat.getChannelCount() == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO, captureFormat.getEncoding()); return new AudioRecord(attributes, captureFormat, bufferSize, event.getCaptureSession()); } @Override @Override public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); runOrAddOperation((opId, service) -> { runOrAddOperation(new Operation( // always execute: () -> { if (!mRecognitionConfig.allowMultipleTriggers) { if (!mRecognitionConfig.allowMultipleTriggers) { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); mCallbacks.remove(mPuuid.getUuid()); } } mDestroyOnceRunningOpsDone = true; mDestroyOnceRunningOpsDone = true; } } }, // execute if not throttled: (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event), // execute if throttled: () -> { if (event.isCaptureAvailable()) { AudioRecord capturedData = createAudioRecordForEvent(event); service.onGenericRecognitionEvent(mPuuid, opId, event); // Currently we need to start and release the audio record to reset }); // the DSP even if we don't want to process the event capturedData.startRecording(); capturedData.release(); } })); } } @Override @Override public void onError(int status) { public void onError(int status) { if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); runOrAddOperation((opId, service) -> { runOrAddOperation( new Operation( // always execute: () -> { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); mCallbacks.remove(mPuuid.getUuid()); } } mDestroyOnceRunningOpsDone = true; mDestroyOnceRunningOpsDone = true; }, service.onError(mPuuid, opId, status); // execute if not throttled: }); (opId, service) -> service.onError(mPuuid, opId, status), // nothing to do if throttled null)); } } @Override @Override Loading Loading
services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +127 −23 Original line number Original line Diff line number Diff line Loading @@ -46,6 +46,10 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.SoundModel; import android.hardware.soundtrigger.SoundTrigger.SoundModel; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.SoundTriggerDetectionService; import android.media.soundtrigger.SoundTriggerDetectionService; Loading Loading @@ -654,10 +658,47 @@ public class SoundTriggerService extends SystemService { } } } } private interface Operation { /** * A single operation run in a {@link RemoteSoundTriggerDetectionService}. * * <p>Once the remote service is connected either setup + execute or setup + stop is executed. */ private static class Operation { private interface ExecuteOp { void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; } } private final @Nullable Runnable mSetupOp; private final @NonNull ExecuteOp mExecuteOp; private final @Nullable Runnable mDropOp; private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp, @Nullable Runnable cancelOp) { mSetupOp = setupOp; mExecuteOp = executeOp; mDropOp = cancelOp; } private void setup() { if (mSetupOp != null) { mSetupOp.run(); } } void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException { setup(); mExecuteOp.run(opId, service); } void drop() { setup(); if (mDropOp != null) { mDropOp.run(); } } } /** /** * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed * when the service connects. * when the service connects. Loading Loading @@ -902,6 +943,10 @@ public class SoundTriggerService extends SystemService { private void runOrAddOperation(Operation op) { private void runOrAddOperation(Operation op) { synchronized (mRemoteServiceLock) { synchronized (mRemoteServiceLock) { if (mIsDestroyed || mDestroyOnceRunningOpsDone) { if (mIsDestroyed || mDestroyOnceRunningOpsDone) { Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for " + "destruction"); op.drop(); return; return; } } Loading @@ -922,9 +967,15 @@ public class SoundTriggerService extends SystemService { int opsAdded = mNumOps.getOpsAdded(); int opsAdded = mNumOps.getOpsAdded(); if (mNumOps.getOpsAdded() >= opsAllowed) { if (mNumOps.getOpsAdded() >= opsAllowed) { try { if (DEBUG || opsAllowed + 10 > opsAdded) { if (DEBUG || opsAllowed + 10 > opsAdded) { Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were " Slog.w(TAG, mPuuid + ": Dropped operation as too many operations " + "run in last 24 hours"); + "were run in last 24 hours"); } op.drop(); } catch (Exception e) { Slog.e(TAG, mPuuid + ": Could not drop operation", e); } } } else { } else { mNumOps.addOp(currentTime); mNumOps.addOp(currentTime); Loading Loading @@ -972,34 +1023,87 @@ public class SoundTriggerService extends SystemService { + ")"); + ")"); } } /** * Create an AudioRecord enough for starting and releasing the data buffered for the event. * * @param event The event that was received * @return The initialized AudioRecord */ private @NonNull AudioRecord createAudioRecordForEvent( @NonNull SoundTrigger.GenericRecognitionEvent event) { AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD); AudioAttributes attributes = attributesBuilder.build(); // Use same AudioFormat processing as in RecognitionEvent.fromParcel AudioFormat originalFormat = event.getCaptureFormat(); AudioFormat captureFormat = (new AudioFormat.Builder()) .setChannelMask(originalFormat.getChannelMask()) .setEncoding(originalFormat.getEncoding()) .setSampleRate(originalFormat.getSampleRate()) .build(); int bufferSize = AudioRecord.getMinBufferSize( captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED ? AudioFormat.SAMPLE_RATE_HZ_MAX : captureFormat.getSampleRate(), captureFormat.getChannelCount() == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO, captureFormat.getEncoding()); return new AudioRecord(attributes, captureFormat, bufferSize, event.getCaptureSession()); } @Override @Override public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); runOrAddOperation((opId, service) -> { runOrAddOperation(new Operation( // always execute: () -> { if (!mRecognitionConfig.allowMultipleTriggers) { if (!mRecognitionConfig.allowMultipleTriggers) { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); mCallbacks.remove(mPuuid.getUuid()); } } mDestroyOnceRunningOpsDone = true; mDestroyOnceRunningOpsDone = true; } } }, // execute if not throttled: (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event), // execute if throttled: () -> { if (event.isCaptureAvailable()) { AudioRecord capturedData = createAudioRecordForEvent(event); service.onGenericRecognitionEvent(mPuuid, opId, event); // Currently we need to start and release the audio record to reset }); // the DSP even if we don't want to process the event capturedData.startRecording(); capturedData.release(); } })); } } @Override @Override public void onError(int status) { public void onError(int status) { if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); runOrAddOperation((opId, service) -> { runOrAddOperation( new Operation( // always execute: () -> { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); mCallbacks.remove(mPuuid.getUuid()); } } mDestroyOnceRunningOpsDone = true; mDestroyOnceRunningOpsDone = true; }, service.onError(mPuuid, opId, status); // execute if not throttled: }); (opId, service) -> service.onError(mPuuid, opId, status), // nothing to do if throttled null)); } } @Override @Override Loading