OSDN Git Service

Simulate handling of event when throttling
authorPhilip P. Moltmann <moltmann@google.com>
Fri, 4 May 2018 20:59:45 +0000 (13:59 -0700)
committerPhilip P. Moltmann <moltmann@google.com>
Fri, 4 May 2018 20:59:45 +0000 (13:59 -0700)
When trottling we need to simulate handling of the event as otherwise
the DSP gets upset and SoundTriggerService and SoundTriggerHelper get
out of sync.

(1) Currently the DSP requires to open and release the AudioRecord if
a event was detected. Hence If we drop and event we need to do the
minimal version of that

(2) If a recognitions is set up with !allowMultipleTriggers the other
parts assume that as soon as one even is handled no further will be
needed. The other parts of the system are not aware of throttling.
Hence even when throttled we have to destroy the service when
!allowMultipleTriggers.

We do this by splitting the ops into three parts.

Setup (always executed): Takes care of problem (2) by checking the flag
and setting the destroy-once-ops-and-handled flag

Execute (not thottled): Send the op to the remote service

Drop (Trottled): Do what is needed if the remote service is not
involved. This handled issue (1)

Test: Caused throttling and saw - AudioRecord started and released
                                - service connection destroyed
Bug: 78212455

Change-Id: I0ff81a7b38d07db1365be7ecc44e93cf329b32d5

services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java

index cd524a5..13e3069 100644 (file)
@@ -46,6 +46,10 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 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.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
@@ -654,8 +658,45 @@ public class SoundTriggerService extends SystemService {
         }
     }
 
-    private interface Operation {
-        void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
+    /**
+     * 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;
+        }
+
+        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();
+            }
+        }
     }
 
     /**
@@ -902,6 +943,10 @@ public class SoundTriggerService extends SystemService {
         private void runOrAddOperation(Operation op) {
             synchronized (mRemoteServiceLock) {
                 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
+                    Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
+                            + "destruction");
+
+                    op.drop();
                     return;
                 }
 
@@ -922,9 +967,15 @@ public class SoundTriggerService extends SystemService {
 
                     int opsAdded = mNumOps.getOpsAdded();
                     if (mNumOps.getOpsAdded() >= opsAllowed) {
-                        if (DEBUG || opsAllowed + 10 > opsAdded) {
-                            Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
-                                    + "run in last 24 hours");
+                        try {
+                            if (DEBUG || opsAllowed + 10 > opsAdded) {
+                                Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
+                                        + "were run in last 24 hours");
+                            }
+
+                            op.drop();
+                        } catch (Exception e) {
+                            Slog.e(TAG, mPuuid + ": Could not drop operation", e);
                         }
                     } else {
                         mNumOps.addOp(currentTime);
@@ -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
         public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
 
-            runOrAddOperation((opId, service) -> {
-                if (!mRecognitionConfig.allowMultipleTriggers) {
-                    synchronized (mCallbacksLock) {
-                        mCallbacks.remove(mPuuid.getUuid());
-                    }
-                    mDestroyOnceRunningOpsDone = true;
-                }
-
-                service.onGenericRecognitionEvent(mPuuid, opId, event);
-            });
+            runOrAddOperation(new Operation(
+                    // always execute:
+                    () -> {
+                        if (!mRecognitionConfig.allowMultipleTriggers) {
+                            // Unregister this remoteService once op is done
+                            synchronized (mCallbacksLock) {
+                                mCallbacks.remove(mPuuid.getUuid());
+                            }
+                            mDestroyOnceRunningOpsDone = true;
+                        }
+                    },
+                    // execute if not throttled:
+                    (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
+                    // execute if throttled:
+                    () -> {
+                        if (event.isCaptureAvailable()) {
+                            AudioRecord capturedData = createAudioRecordForEvent(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
         public void onError(int status) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
-            runOrAddOperation((opId, service) -> {
-                synchronized (mCallbacksLock) {
-                    mCallbacks.remove(mPuuid.getUuid());
-                }
-                mDestroyOnceRunningOpsDone = true;
-
-                service.onError(mPuuid, opId, status);
-            });
+            runOrAddOperation(
+                    new Operation(
+                            // always execute:
+                            () -> {
+                                // Unregister this remoteService once op is done
+                                synchronized (mCallbacksLock) {
+                                    mCallbacks.remove(mPuuid.getUuid());
+                                }
+                                mDestroyOnceRunningOpsDone = true;
+                            },
+                            // execute if not throttled:
+                            (opId, service) -> service.onError(mPuuid, opId, status),
+                            // nothing to do if throttled
+                            null));
         }
 
         @Override