Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5b4403a authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Simulate handling of event when throttling

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
parent 8aec7be1
Loading
Loading
Loading
Loading
+127 −23
Original line number Diff line number Diff line
@@ -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,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;
        }

        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
     * when the service connects.
@@ -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) {
                        try {
                            if (DEBUG || opsAllowed + 10 > opsAdded) {
                            Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
                                    + "run in last 24 hours");
                                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) -> {
            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);

                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
        public void onError(int 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) {
                                    mCallbacks.remove(mPuuid.getUuid());
                                }
                                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