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

Commit 3fff7f56 authored by Arunesh Mishra's avatar Arunesh Mishra
Browse files

SoundTriggerHelper changes for GenericSoundModels.

 - Refactoring SoundTriggerHelper to handle generic sound models.
     - Ability to store multiple models, callback and state information.
     - Separate out initialization to be done per voice model, per any model
     and per generic model.
 - Minor change to the API exposed -- removing the Handler from the
   createSoundTriggerDetector call.
 - Added callback processing for onRecognitionEvent().
 - Added logic for stopAll().
 - Changes to the SoundTriggerTestApp to start/stop recognition.
     - Multiple models (3).
     - Ability to start/stop/load/unload individual models.

Bug: 22860713
Bug: 27222043
Change-Id: Ie5d811babb956bead653fb560a43f1e549ed11bd
parent 93dfc03b
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -24281,19 +24281,26 @@ package android.media.session {
package android.media.soundtrigger {
  public final class SoundTriggerDetector {
    method public boolean startRecognition();
    method public boolean startRecognition(int);
    method public boolean stopRecognition();
    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
  }
  public abstract class SoundTriggerDetector.Callback {
  public static abstract class SoundTriggerDetector.Callback {
    ctor public SoundTriggerDetector.Callback();
    method public abstract void onAvailabilityChanged(int);
    method public abstract void onDetected();
    method public abstract void onDetected(android.media.soundtrigger.SoundTriggerDetector.EventPayload);
    method public abstract void onError();
    method public abstract void onRecognitionPaused();
    method public abstract void onRecognitionResumed();
  }
  public static class SoundTriggerDetector.EventPayload {
    method public android.media.AudioFormat getCaptureAudioFormat();
    method public byte[] getTriggerAudio();
  }
  public final class SoundTriggerManager {
    method public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, android.media.soundtrigger.SoundTriggerDetector.Callback, android.os.Handler);
    method public void deleteModel(java.util.UUID);
+3 −2
Original line number Diff line number Diff line
@@ -33,10 +33,11 @@ interface ISoundTriggerService {

    void deleteSoundModel(in ParcelUuid soundModelId);

    void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
    int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
         in SoundTrigger.RecognitionConfig config);

    /**
     * Stops recognition.
     */
    void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
    int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
}
+192 −12
Original line number Diff line number Diff line
@@ -16,12 +16,17 @@

package android.media.soundtrigger;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Slog;
@@ -29,6 +34,8 @@ import android.util.Slog;
import com.android.internal.app.ISoundTriggerService;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID;

/**
@@ -45,6 +52,12 @@ public final class SoundTriggerDetector {
    private static final boolean DBG = false;
    private static final String TAG = "SoundTriggerDetector";

    private static final int MSG_AVAILABILITY_CHANGED = 1;
    private static final int MSG_SOUND_TRIGGER_DETECTED = 2;
    private static final int MSG_DETECTION_ERROR = 3;
    private static final int MSG_DETECTION_PAUSE = 4;
    private static final int MSG_DETECTION_RESUME = 5;

    private final Object mLock = new Object();

    private final ISoundTriggerService mSoundTriggerService;
@@ -53,7 +66,121 @@ public final class SoundTriggerDetector {
    private final Handler mHandler;
    private final RecognitionCallback mRecognitionCallback;

    public abstract class Callback {
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true,
            value = {
                RECOGNITION_FLAG_NONE,
                RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
            })
    public @interface RecognitionFlags {}

    /**
     * Empty flag for {@link #startRecognition(int)}.
     *
     *  @hide
     */
    public static final int RECOGNITION_FLAG_NONE = 0;

    /**
     * Recognition flag for {@link #startRecognition(int)} that indicates
     * whether the trigger audio for hotword needs to be captured.
     */
    public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;

    /**
     * Recognition flag for {@link #startRecognition(int)} that indicates
     * whether the recognition should keep going on even after the
     * model triggers.
     * If this flag is specified, it's possible to get multiple
     * triggers after a call to {@link #startRecognition(int)}, if the model
     * triggers multiple times.
     * When this isn't specified, the default behavior is to stop recognition once the
     * trigger happenss, till the caller starts recognition again.
     */
    public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;

    /**
     * Additional payload for {@link Callback#onDetected}.
     */
    public static class EventPayload {
        private final boolean mTriggerAvailable;

        // Indicates if {@code captureSession} can be used to continue capturing more audio
        // from the DSP hardware.
        private final boolean mCaptureAvailable;
        // The session to use when attempting to capture more audio from the DSP hardware.
        private final int mCaptureSession;
        private final AudioFormat mAudioFormat;
        // Raw data associated with the event.
        // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
        private final byte[] mData;

        private EventPayload(boolean triggerAvailable, boolean captureAvailable,
                AudioFormat audioFormat, int captureSession, byte[] data) {
            mTriggerAvailable = triggerAvailable;
            mCaptureAvailable = captureAvailable;
            mCaptureSession = captureSession;
            mAudioFormat = audioFormat;
            mData = data;
        }

        /**
         * Gets the format of the audio obtained using {@link #getTriggerAudio()}.
         * May be null if there's no audio present.
         */
        @Nullable
        public AudioFormat getCaptureAudioFormat() {
            return mAudioFormat;
        }

        /**
         * Gets the raw audio that triggered the keyphrase.
         * This may be null if the trigger audio isn't available.
         * If non-null, the format of the audio can be obtained by calling
         * {@link #getCaptureAudioFormat()}.
         *
         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
         */
        @Nullable
        public byte[] getTriggerAudio() {
            if (mTriggerAvailable) {
                return mData;
            } else {
                return null;
            }
        }

        /**
         * Gets the session ID to start a capture from the DSP.
         * This may be null if streaming capture isn't possible.
         * If non-null, the format of the audio that can be captured can be
         * obtained using {@link #getCaptureAudioFormat()}.
         *
         * TODO: Candidate for Public API when the API to start capture with a session ID
         * is made public.
         *
         * TODO: Add this to {@link #getCaptureAudioFormat()}:
         * "Gets the format of the audio obtained using {@link #getTriggerAudio()}
         * or {@link #getCaptureSession()}. May be null if no audio can be obtained
         * for either the trigger or a streaming session."
         *
         * TODO: Should this return a known invalid value instead?
         *
         * @hide
         */
        @Nullable
        public Integer getCaptureSession() {
            if (mCaptureAvailable) {
                return mCaptureSession;
            } else {
                return null;
            }
        }
    }

    public static abstract class Callback {
        /**
         * Called when the availability of the sound model changes.
         */
@@ -63,7 +190,7 @@ public final class SoundTriggerDetector {
         * Called when the sound model has triggered (such as when it matched a
         * given sound pattern).
         */
        public abstract void onDetected();
        public abstract void onDetected(@NonNull EventPayload eventPayload);

        /**
         *  Called when the detection fails due to an error.
@@ -95,9 +222,9 @@ public final class SoundTriggerDetector {
        mSoundModelId = soundModelId;
        mCallback = callback;
        if (handler == null) {
            mHandler = new Handler();
            mHandler = new MyHandler();
        } else {
            mHandler = handler;
            mHandler = new MyHandler(handler.getLooper());
        }
        mRecognitionCallback = new RecognitionCallback();
    }
@@ -107,13 +234,19 @@ public final class SoundTriggerDetector {
     * {@link Callback}.
     * @return Indicates whether the call succeeded or not.
     */
    public boolean startRecognition() {
    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
        if (DBG) {
            Slog.d(TAG, "startRecognition()");
        }
        boolean captureTriggerAudio =
                (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;

        boolean allowMultipleTriggers =
                (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
        try {
            mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
                    mRecognitionCallback);
                    mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
                        allowMultipleTriggers, null, null));
        } catch (RemoteException e) {
            return false;
        }
@@ -144,17 +277,25 @@ public final class SoundTriggerDetector {

    /**
     * Callback that handles events from the lower sound trigger layer.
     *
     * Note that these callbacks will be called synchronously from the SoundTriggerService
     * layer and thus should do minimal work (such as sending a message on a handler to do
     * the real work).
     * @hide
     */
    private static class RecognitionCallback extends
            IRecognitionStatusCallback.Stub {
    private class RecognitionCallback extends IRecognitionStatusCallback.Stub {

        /**
         * @hide
         */
        @Override
        public void onDetected(SoundTrigger.RecognitionEvent event) {
            Slog.e(TAG, "onDetected()" + event);
            Slog.d(TAG, "onDetected()" + event);
            Message.obtain(mHandler,
                    MSG_SOUND_TRIGGER_DETECTED,
                    new EventPayload(event.triggerInData, event.captureAvailable,
                            event.captureFormat, event.captureSession, event.data))
                    .sendToTarget();
        }

        /**
@@ -162,7 +303,8 @@ public final class SoundTriggerDetector {
         */
        @Override
        public void onError(int status) {
            Slog.e(TAG, "onError()" + status);
            Slog.d(TAG, "onError()" + status);
            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
        }

        /**
@@ -170,7 +312,8 @@ public final class SoundTriggerDetector {
         */
        @Override
        public void onRecognitionPaused() {
            Slog.e(TAG, "onRecognitionPaused()");
            Slog.d(TAG, "onRecognitionPaused()");
            mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
        }

        /**
@@ -178,7 +321,44 @@ public final class SoundTriggerDetector {
         */
        @Override
        public void onRecognitionResumed() {
            Slog.e(TAG, "onRecognitionResumed()");
            Slog.d(TAG, "onRecognitionResumed()");
            mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
        }
    }

    private class MyHandler extends Handler {

        MyHandler() {
            super();
        }

        MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mCallback == null) {
                  Slog.w(TAG, "Received message: " + msg.what + " for NULL callback.");
                  return;
            }
            switch (msg.what) {
                case MSG_SOUND_TRIGGER_DETECTED:
                    mCallback.onDetected((EventPayload) msg.obj);
                    break;
                case MSG_DETECTION_ERROR:
                    mCallback.onError();
                    break;
                case MSG_DETECTION_PAUSE:
                    mCallback.onRecognitionPaused();
                    break;
                case MSG_DETECTION_RESUME:
                    mCallback.onRecognitionResumed();
                    break;
                default:
                    super.handleMessage(msg);

            }
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper {
    private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
            + GenericSoundModelContract.TABLE + "("
            + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
            + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
            + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";


+571 −95

File changed.

Preview size limit exceeded, changes collapsed.

Loading