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

Commit 21d375cd authored by Eric Laurent's avatar Eric Laurent Committed by Android (Google) Code Review
Browse files

Merge "AudioRecord: Allow to share capture history." into sc-dev

parents f941d692 c670e061
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -23413,11 +23413,14 @@ package android.media {
    method public void onError(@NonNull android.media.MediaSync, int, int);
  }
  public class MediaSyncEvent {
  public class MediaSyncEvent implements android.os.Parcelable {
    method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
    method public int describeContents();
    method public int getAudioSessionId();
    method public int getType();
    method public android.media.MediaSyncEvent setAudioSessionId(int) throws java.lang.IllegalArgumentException;
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaSyncEvent> CREATOR;
    field public static final int SYNC_EVENT_NONE = 0; // 0x0
    field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1
  }
+8 −0
Original line number Diff line number Diff line
@@ -5271,11 +5271,15 @@ package android.media {
  public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
    ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
    method public static long getMaxSharedAudioHistoryMillis();
    method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.MediaSyncEvent shareAudioHistory(@NonNull String, @IntRange(from=0) long);
  }
  public static class AudioRecord.Builder {
    method public android.media.AudioRecord.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
    method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setMaxSharedAudioHistoryMillis(long) throws java.lang.IllegalArgumentException;
    method public android.media.AudioRecord.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
    method @NonNull public android.media.AudioRecord.Builder setSharedAudioEvent(@NonNull android.media.MediaSyncEvent) throws java.lang.IllegalArgumentException;
  }
  public final class AudioRecordingConfiguration implements android.os.Parcelable {
@@ -5338,6 +5342,10 @@ package android.media {
    method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
  }
  public class MediaSyncEvent implements android.os.Parcelable {
    field public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; // 0x64
  }
  public class PlayerProxy {
    method public void pause();
    method public void setPan(float);
+34 −16
Original line number Diff line number Diff line
@@ -189,7 +189,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
                                            jobject jaa, jintArray jSampleRate, jint channelMask,
                                            jint channelIndexMask, jint audioFormat,
                                            jint buffSizeInBytes, jintArray jSession,
                                            jobject jIdentity, jlong nativeRecordInJavaObj) {
                                            jobject jIdentity, jlong nativeRecordInJavaObj,
                                            jint sharedAudioHistoryMs) {
    //ALOGV(">> Entering android_media_AudioRecord_setup");
    //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
    //     "nativeRecordInJavaObj=0x%llX",
@@ -288,20 +289,18 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
        lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
        lpCallbackData->busy = false;

        const status_t status = lpRecorder->set(paa->source,
            sampleRateInHertz,
        const status_t status =
                lpRecorder->set(paa->source, sampleRateInHertz,
                                format, // word length, PCM
            localChanMask,
            frameCount,
                                localChanMask, frameCount,
                                recorderCallback, // callback_t
                                lpCallbackData,   // void* user
                                0,                // notificationFrames,
                                true,             // threadCanCallJava
            sessionId,
            AudioRecord::TRANSFER_DEFAULT,
            flags,
            -1, -1,        // default uid, pid
            paa.get());
                                sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1,
                                -1, // default uid, pid
                                paa.get(), AUDIO_PORT_HANDLE_NONE, MIC_DIRECTION_UNSPECIFIED,
                                MIC_FIELD_DIMENSION_DEFAULT, sharedAudioHistoryMs);

        if (status != NO_ERROR) {
            ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
@@ -877,6 +876,23 @@ static void android_media_AudioRecord_setLogSessionId(JNIEnv *env, jobject thiz,
    record->setLogSessionId(logSessionId.c_str());
}

static jint android_media_AudioRecord_shareAudioHistory(JNIEnv *env, jobject thiz,
                                                        jstring jSharedPackageName,
                                                        jlong jSharedStartMs) {
    sp<AudioRecord> record = getAudioRecord(env, thiz);
    if (record == nullptr) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "Unable to retrieve AudioRecord pointer for setLogSessionId()");
    }
    if (jSharedPackageName == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "package name cannot be null");
    }
    ScopedUtfChars nSharedPackageName(env, jSharedPackageName);
    ALOGV("%s: nSharedPackageName '%s'", __func__, nSharedPackageName.c_str());
    return nativeToJavaStatus(record->shareAudioHistory(nSharedPackageName.c_str(),
                                                        static_cast<int64_t>(jSharedStartMs)));
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_port_id(JNIEnv *env,  jobject thiz) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
@@ -896,7 +912,7 @@ static const JNINativeMethod gMethods[] = {
        {"native_start", "(II)I", (void *)android_media_AudioRecord_start},
        {"native_stop", "()V", (void *)android_media_AudioRecord_stop},
        {"native_setup",
         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;J)I",
         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I",
         (void *)android_media_AudioRecord_setup},
        {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
        {"native_release", "()V", (void *)android_media_AudioRecord_release},
@@ -936,6 +952,8 @@ static const JNINativeMethod gMethods[] = {
         (void *)android_media_AudioRecord_set_preferred_microphone_field_dimension},
        {"native_setLogSessionId", "(Ljava/lang/String;)V",
         (void *)android_media_AudioRecord_setLogSessionId},
        {"native_shareAudioHistory", "(Ljava/lang/String;J)I",
         (void *)android_media_AudioRecord_shareAudioHistory},
};

// field names found in android/media/AudioRecord.java
+139 −10
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.media.permission.PermissionUtil.myIdentity;
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -358,7 +359,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
    @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
    public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int sessionId) throws IllegalArgumentException {
        this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication());
        this(attributes, format, bufferSizeInBytes, sessionId,
                ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/);
    }

    /**
@@ -383,7 +385,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
     * @throws IllegalArgumentException
     */
    private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int sessionId, @Nullable Context context) throws IllegalArgumentException {
            int sessionId, @Nullable Context context,
            int maxSharedAudioHistoryMs) throws IllegalArgumentException {
        mRecordingState = RECORDSTATE_STOPPED;

        if (attributes == null) {
@@ -455,12 +458,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        int[] sampleRate = new int[] {mSampleRate};
        int[] session = new int[1];
        session[0] = sessionId;

        //TODO: update native initialization when information about hardware init failure
        //      due to capture device already open is available.
        int initResult = native_setup(new WeakReference<AudioRecord>(this),
                mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
                mAudioFormat, mNativeBufferSizeInBytes,
                session, identity, 0 /*nativeRecordInJavaObj*/);
                session, identity, 0 /*nativeRecordInJavaObj*/,
                maxSharedAudioHistoryMs);
        if (initResult != SUCCESS) {
            loge("Error code "+initResult+" when initializing native AudioRecord object.");
            return; // with mState == STATE_UNINITIALIZED
@@ -522,7 +527,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
                    0 /*mNativeBufferSizeInBytes*/,
                    session,
                    myIdentity(null),
                    nativeRecordInJavaObj);
                    nativeRecordInJavaObj,
                    0);
            if (initResult != SUCCESS) {
                loge("Error code "+initResult+" when initializing native AudioRecord object.");
                return; // with mState == STATE_UNINITIALIZED
@@ -581,7 +587,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        private int mBufferSizeInBytes;
        private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
        private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;

        private int mMaxSharedAudioHistoryMs = 0;
        private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
        private static final int PRIVACY_SENSITIVE_DISABLED = 0;
        private static final int PRIVACY_SENSITIVE_ENABLED = 1;
@@ -747,7 +753,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
            if (sessionId < 0) {
                throw new IllegalArgumentException("Invalid session ID " + sessionId);
            }
            // Do not override a session ID previously set with setSharedAudioEvent()
            if (mSessionId == AudioManager.AUDIO_SESSION_ID_GENERATE) {
                mSessionId = sessionId;
            } else {
                Log.e(TAG, "setSessionId() called twice or after setSharedAudioEvent()");
            }
            return this;
        }

@@ -771,6 +782,57 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
            return record;
        }

        /**
         * @hide
         * Specifies the maximum duration in the past of the this AudioRecord's capture buffer
         * that can be shared with another app by calling
         * {@link AudioRecord#shareAudioHistory(String, long)}.
         * @param maxSharedAudioHistoryMillis the maximum duration that will be available
         *                                    in milliseconds.
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         *
         */
        @SystemApi
        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
        public @NonNull Builder setMaxSharedAudioHistoryMillis(long maxSharedAudioHistoryMillis)
                throws IllegalArgumentException {
            if (maxSharedAudioHistoryMillis <= 0
                    || maxSharedAudioHistoryMillis > MAX_SHARED_AUDIO_HISTORY_MS) {
                throw new IllegalArgumentException("Illegal maxSharedAudioHistoryMillis argument");
            }
            mMaxSharedAudioHistoryMs = (int) maxSharedAudioHistoryMillis;
            return this;
        }

        /**
         * @hide
         * Indicates that this AudioRecord will use the audio history shared by another app's
         * AudioRecord. See {@link AudioRecord#shareAudioHistory(String, long)}.
         * The audio session ID set with {@link AudioRecord.Builder#setSessionId(int)} will be
         * ignored if this method is used.
         * @param event The {@link MediaSyncEvent} provided by the app sharing its audio history
         *              with this AudioRecord.
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public @NonNull Builder setSharedAudioEvent(@NonNull MediaSyncEvent event)
                throws IllegalArgumentException {
            Objects.requireNonNull(event);
            if (event.getType() != MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY) {
                throw new IllegalArgumentException(
                        "Invalid event type " + event.getType());
            }
            if (event.getAudioSessionId() == AudioSystem.AUDIO_SESSION_ALLOCATE) {
                throw new IllegalArgumentException(
                        "Invalid session ID " + event.getAudioSessionId());
            }
            // This prevails over a session ID set with setSessionId()
            mSessionId = event.getAudioSessionId();
            return this;
        }

        /**
         * @return a new {@link AudioRecord} instance successfully initialized with all
         *     the parameters set on this <code>Builder</code>.
@@ -837,7 +899,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
                            * mFormat.getBytesPerSample(mFormat.getEncoding());
                }
                final AudioRecord record = new AudioRecord(
                        mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext);
                        mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext,
                                    mMaxSharedAudioHistoryMs);
                if (record.getState() == STATE_UNINITIALIZED) {
                    // release is not necessary
                    throw new UnsupportedOperationException("Cannot create AudioRecord");
@@ -1423,7 +1486,6 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
                || (offsetInShorts + sizeInShorts > audioData.length)) {
            return ERROR_BAD_VALUE;
        }

        return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts,
                readMode == READ_BLOCKING);
    }
@@ -1642,6 +1704,70 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS);
    }

    /**
     * Must match the native definition in frameworks/av/service/audioflinger/Audioflinger.h.
     */
    private static final long MAX_SHARED_AUDIO_HISTORY_MS = 5000;

    /**
     * @hide
     * returns the maximum duration in milliseconds of the audio history that can be requested
     * to be made available to other clients using the same session with
     * {@Link Builder#setMaxSharedAudioHistory(long)}.
     */
    @SystemApi
    public static long getMaxSharedAudioHistoryMillis() {
        return MAX_SHARED_AUDIO_HISTORY_MS;
    }

    /**
     * @hide
     *
     * A privileged app with permission CAPTURE_AUDIO_HOTWORD can share part of its recent
     * capture history on a given AudioRecord with the following steps:
     * 1) Specify the maximum time in the past that will be available for other apps by calling
     * {@link Builder#setMaxSharedAudioHistoryMillis(long)} when creating the AudioRecord.
     * 2) Start recording and determine where the other app should start capturing in the past.
     * 3) Call this method with the package name of the app the history will be shared with and
     * the intended start time for this app's capture relative to this AudioRecord's start time.
     * 4) Communicate the {@link MediaSyncEvent} returned by this method to the other app.
     * 5) The other app will use the MediaSyncEvent when creating its AudioRecord with
     * {@link Builder#setSharedAudioEvent(MediaSyncEvent).
     * 6) Only after the other app has started capturing can this app stop capturing and
     * release its AudioRecord.
     * This method is intended to be called only once: if called multiple times, only the last
     * request will be honored.
     * The implementation is "best effort": if the specified start time if too far in the past
     * compared to the max available history specified, the start time will be adjusted to the
     * start of the available history.
     * @param sharedPackage the package the history will be shared with
     * @param startFromMillis the start time, relative to the initial start time of this
     *        AudioRecord, at which the other AudioRecord will start.
     * @return a {@link MediaSyncEvent} to be communicated to the app this AudioRecord's audio
     *         history will be shared with.
     * @throws IllegalArgumentException
     * @throws SecurityException
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
    @NonNull public MediaSyncEvent shareAudioHistory(@NonNull String sharedPackage,
                                  @IntRange(from = 0) long startFromMillis) {
        Objects.requireNonNull(sharedPackage);
        if (startFromMillis < 0) {
            throw new IllegalArgumentException("Illegal negative sharedAudioHistoryMs argument");
        }
        int status = native_shareAudioHistory(sharedPackage, startFromMillis);
        if (status == AudioSystem.BAD_VALUE) {
            throw new IllegalArgumentException("Illegal sharedAudioHistoryMs argument");
        } else if (status == AudioSystem.PERMISSION_DENIED) {
            throw new SecurityException("permission CAPTURE_AUDIO_HOTWORD required");
        }
        MediaSyncEvent event =
                MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY);
        event.setAudioSessionId(mSessionId);
        return event;
    }

    /*
     * Call BEFORE adding a routing callback handler.
     */
@@ -2105,13 +2231,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        identity.packageName = opPackageName;

        return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask,
                audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj);
                audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0);
    }

    private native int native_setup(Object audiorecordThis,
            Object /*AudioAttributes*/ attributes,
            int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
            int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj);
            int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj,
            int maxSharedAudioHistoryMs);

    // TODO remove: implementation calls directly into implementation of native_release()
    private native void native_finalize();
@@ -2170,6 +2297,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,

    private native void native_setLogSessionId(@Nullable String logSessionId);

    private native int native_shareAudioHistory(@NonNull String sharedPackage, long startFromMs);

    //---------------------------------------------------------
    // Utility methods
    //------------------
+4 −0
Original line number Diff line number Diff line
@@ -1464,6 +1464,10 @@ public class AudioSystem
    // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
    /** @hide */ public static final int SYNC_EVENT_NONE = 0;
    /** @hide */ public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
    /** @hide
     *  Not used by native implementation.
     *  See {@link AudioRecord.Builder#setSharedAudioEvent(MediaSyncEvent) */
    public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100;

    /**
     * @hide
Loading