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

Commit 701d6ff1 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

AudioAttributes and AudioRecord.

Add audio recording source in AudioAttributes.
New AudioRecord constructor with AudioAttributes and
  AudioFormat.
Legacy AudioRecord constructor is calling into the new
  constructor.

Bug 16009464

Change-Id: I69d81f9e71bdf946c4cfbda6d3d8552ffac6b5c2
parent 57188b89
Loading
Loading
Loading
Loading
+46 −10
Original line number Diff line number Diff line
@@ -35,14 +35,20 @@ using namespace android;

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioRecord";
static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";

struct fields_t {
struct audio_record_fields_t {
    // these fields provide access from C++ to the...
    jmethodID postNativeEventInJava; //... event post callback method
    jfieldID  nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object
    jfieldID  nativeCallbackCookie;    // provides access to the AudioRecord callback data
};
static fields_t javaAudioRecordFields;
struct audio_attributes_fields_t {
    jfieldID  fieldRecSource;    // AudioAttributes.mSource
    jfieldID  fieldFormattedTags;// AudioAttributes.mFormattedTags
};
static audio_attributes_fields_t javaAudioAttrFields;
static audio_record_fields_t     javaAudioRecordFields;

struct audiorecord_callback_cookie {
    jclass      audioRecord_class;
@@ -138,7 +144,7 @@ static sp<AudioRecord> setAudioRecord(JNIEnv* env, jobject thiz, const sp<AudioR
// ----------------------------------------------------------------------------
static jint
android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jint source, jint sampleRateInHertz, jint channelMask,
        jobject jaa, jint sampleRateInHertz, jint channelMask,
                // Java channel masks map directly to the native definition
        jint audioFormat, jint buffSizeInBytes, jintArray jSession)
{
@@ -146,6 +152,11 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d",
    //     sampleRateInHertz, audioFormat, channelMask, buffSizeInBytes);

    if (jaa == 0) {
        ALOGE("Error creating AudioRecord: invalid audio attributes");
        return (jint) AUDIO_JAVA_ERROR;
    }

    if (!audio_is_input_channel(channelMask)) {
        ALOGE("Error creating AudioRecord: channel mask %#x is not valid.", channelMask);
        return (jint) AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK;
@@ -168,11 +179,6 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    size_t frameSize = channelCount * bytesPerSample;
    size_t frameCount = buffSizeInBytes / frameSize;

    if ((uint32_t(source) >= AUDIO_SOURCE_CNT) && (uint32_t(source) != AUDIO_SOURCE_HOTWORD)) {
        ALOGE("Error creating AudioRecord: unknown source %d.", source);
        return (jint) AUDIORECORD_ERROR_SETUP_INVALIDSOURCE;
    }

    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find %s when setting up callback.", kClassPathName);
@@ -196,6 +202,19 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    // create an uninitialized AudioRecord object
    sp<AudioRecord> lpRecorder = new AudioRecord();

    audio_attributes_t *paa = NULL;
    // read the AudioAttributes values
    paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
    const jstring jtags =
            (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
    const char* tags = env->GetStringUTFChars(jtags, NULL);
    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
    strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
    env->ReleaseStringUTFChars(jtags, tags);
    paa->source = (audio_source_t) env->GetIntField(jaa, javaAudioAttrFields.fieldRecSource);

    ALOGV("AudioRecord_setup for source=%d tags=%s", paa->source, paa->tags);

    // create the callback information:
    // this data will be passed with every AudioRecord callback
    audiorecord_callback_cookie *lpCallbackData = new audiorecord_callback_cookie;
@@ -204,7 +223,7 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
    lpCallbackData->busy = false;

    const status_t status = lpRecorder->set((audio_source_t) source,
    const status_t status = lpRecorder->set(paa->source,
        sampleRateInHertz,
        format,        // word length, PCM
        channelMask,
@@ -545,7 +564,7 @@ static JNINativeMethod gMethods[] = {
    // name,               signature,  funcPtr
    {"native_start",         "(II)I",    (void *)android_media_AudioRecord_start},
    {"native_stop",          "()V",    (void *)android_media_AudioRecord_stop},
    {"native_setup",         "(Ljava/lang/Object;IIIII[I)I",
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/Object;IIII[I)I",
                                       (void *)android_media_AudioRecord_setup},
    {"native_finalize",      "()V",    (void *)android_media_AudioRecord_finalize},
    {"native_release",       "()V",    (void *)android_media_AudioRecord_release},
@@ -611,6 +630,23 @@ int register_android_media_AudioRecord(JNIEnv *env)
        return -1;
    }

    // Get the AudioAttributes class and fields
    jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
    if (audioAttrClass == NULL) {
        ALOGE("Can't find %s", kAudioAttributesClassPathName);
        return -1;
    }
    jclass audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
    javaAudioAttrFields.fieldRecSource = env->GetFieldID(audioAttributesClassRef, "mSource", "I");
    javaAudioAttrFields.fieldFormattedTags =
            env->GetFieldID(audioAttributesClassRef, "mFormattedTags", "Ljava/lang/String;");
    env->DeleteGlobalRef(audioAttributesClassRef);
    if (javaAudioAttrFields.fieldRecSource == NULL
            || javaAudioAttrFields.fieldFormattedTags == NULL) {
        ALOGE("Can't initialize AudioAttributes fields");
        return -1;
    }

    return AndroidRuntime::registerNativeMethods(env,
            kClassPathName, gMethods, NELEM(gMethods));
}
+10 −8
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ struct audio_attributes_fields_t {
    jfieldID  fieldUsage;        // AudioAttributes.mUsage
    jfieldID  fieldContentType;  // AudioAttributes.mContentType
    jfieldID  fieldFlags;        // AudioAttributes.mFlags
    jfieldID  fieldTags;        // AudioAttributes.mTags
    jfieldID  fieldFormattedTags;// AudioAttributes.mFormattedTags
};
static audio_track_fields_t      javaAudioTrackFields;
static audio_attributes_fields_t javaAudioAttrFields;
@@ -263,7 +263,8 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    audio_attributes_t *paa = NULL;
    // read the AudioAttributes values
    paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
    const jstring jtags    = (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldTags);
    const jstring jtags =
            (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
    const char* tags = env->GetStringUTFChars(jtags, NULL);
    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
    strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
@@ -1112,11 +1113,12 @@ int register_android_media_AudioTrack(JNIEnv *env)
    javaAudioAttrFields.fieldContentType
                                   = env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
    javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I");
    javaAudioAttrFields.fieldTags  = env->GetFieldID(audioAttributesClassRef, "mFormattedTags",
            "Ljava/lang/String;");
    javaAudioAttrFields.fieldFormattedTags =
            env->GetFieldID(audioAttributesClassRef, "mFormattedTags", "Ljava/lang/String;");
    env->DeleteGlobalRef(audioAttributesClassRef);
    if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL
            || javaAudioAttrFields.fieldFlags == NULL || javaAudioAttrFields.fieldTags == NULL) {
            || javaAudioAttrFields.fieldFlags == NULL
            || javaAudioAttrFields.fieldFormattedTags == NULL) {
        ALOGE("Can't initialize AudioAttributes fields");
        return -1;
    }
+61 −2
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ public final class AudioAttributes implements Parcelable {

    private int mUsage = USAGE_UNKNOWN;
    private int mContentType = CONTENT_TYPE_UNKNOWN;
    private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
    private int mFlags = 0x0;
    private HashSet<String> mTags;
    private String mFormattedTags;
@@ -176,6 +177,17 @@ public final class AudioAttributes implements Parcelable {
        return mUsage;
    }

    /**
     * @hide
     * CANDIDATE FOR PUBLIC API
     * Return the capture preset.
     * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
     *    negative value if none has been set.
     */
    public int getCapturePreset() {
        return mSource;
    }

    /**
     * Return the flags.
     * @return a combined mask of all flags
@@ -210,6 +222,7 @@ public final class AudioAttributes implements Parcelable {
    public static class Builder {
        private int mUsage = USAGE_UNKNOWN;
        private int mContentType = CONTENT_TYPE_UNKNOWN;
        private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
        private int mFlags = 0x0;
        private HashSet<String> mTags = new HashSet<String>();

@@ -241,6 +254,7 @@ public final class AudioAttributes implements Parcelable {
            AudioAttributes aa = new AudioAttributes();
            aa.mContentType = mContentType;
            aa.mUsage = mUsage;
            aa.mSource = mSource;
            aa.mFlags = mFlags;
            aa.mTags = (HashSet<String>) mTags.clone();
            final Iterator<String> tagIterator = mTags.iterator();
@@ -397,9 +411,52 @@ public final class AudioAttributes implements Parcelable {
                    mContentType = CONTENT_TYPE_SPEECH;
                    break;
                default:
                    Log.e(TAG, "Invalid stream type " + streamType + " in for AudioAttributes");
                    Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
            }
            return this;
        }

        /**
         * @hide
         * CANDIDATE FOR PUBLIC API
         * Sets the capture preset.
         * Use this audio attributes configuration method when building an {@link AudioRecord}
         * instance with {@link AudioRecord#AudioRecord(AudioAttributes, AudioFormat, int)}.
         * @param preset one of {@link MediaRecorder.AudioSource#DEFAULT},
         *     {@link MediaRecorder.AudioSource#MIC}, {@link MediaRecorder.AudioSource#CAMCORDER},
         *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} or
         *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
         * @return the same Builder instance.
         */
        public Builder setCapturePreset(int preset) {
            switch (preset) {
                case MediaRecorder.AudioSource.DEFAULT:
                case MediaRecorder.AudioSource.MIC:
                case MediaRecorder.AudioSource.CAMCORDER:
                case MediaRecorder.AudioSource.VOICE_RECOGNITION:
                case MediaRecorder.AudioSource.VOICE_COMMUNICATION:
                    mSource = preset;
                    break;
                default:
                    Log.e(TAG, "Invalid capture preset " + preset + " for AudioAttributes");
            }
            return this;
        }

        /**
         * @hide
         * Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD and
         * REMOTE_SUBMIX.
         * @param preset
         * @return the same Builder instance.
         */
        public Builder setInternalCapturePreset(int preset) {
            if ((preset == MediaRecorder.AudioSource.HOTWORD)
                    || (preset == MediaRecorder.AudioSource.REMOTE_SUBMIX)) {
                mSource = preset;
            } else {
                setCapturePreset(preset);
            }
            mUsage = usageForLegacyStreamType(streamType);
            return this;
        }
    };
@@ -425,6 +482,7 @@ public final class AudioAttributes implements Parcelable {
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mUsage);
        dest.writeInt(mContentType);
        dest.writeInt(mSource);
        dest.writeInt(mFlags);
        dest.writeInt(flags & ALL_PARCEL_FLAGS);
        if ((flags & FLATTEN_TAGS) == 0) {
@@ -439,6 +497,7 @@ public final class AudioAttributes implements Parcelable {
    private AudioAttributes(Parcel in) {
        mUsage = in.readInt();
        mContentType = in.readInt();
        mSource = in.readInt();
        mFlags = in.readInt();
        boolean hasFlattenedTags = ((in.readInt() & FLATTEN_TAGS) == FLATTEN_TAGS);
        mTags = new HashSet<String>();
+9 −0
Original line number Diff line number Diff line
@@ -126,6 +126,15 @@ public class AudioFormat {
            CHANNEL_OUT_LOW_FREQUENCY);
    // CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL

    /**
     * @hide
     * Return the number of channels from an input channel mask
     * @param mask a combination of the CHANNEL_IN_* definitions, even CHANNEL_IN_DEFAULT
     * @return number of channels for the mask
     */
    public static int channelCountFromInChannelMask(int mask) {
        return Integer.bitCount(mask);
    }
    /**
     * @hide
     * Return the number of channels from an output channel mask
+108 −35
Original line number Diff line number Diff line
@@ -180,6 +180,10 @@ public class AudioRecord
     * Audio session ID
     */
    private int mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
    /**
     * AudioAttributes
     */
    private AudioAttributes mAudioAttributes;

    //---------------------------------------------------------
    // Constructor, Finalize
@@ -189,8 +193,8 @@ public class AudioRecord
     * Though some invalid parameters will result in an {@link IllegalArgumentException} exception,
     * other errors do not.  Thus you should call {@link #getState()} immediately after construction
     * to confirm that the object is usable.
     * @param audioSource the recording source. See {@link MediaRecorder.AudioSource} for
     *    recording source definitions.
     * @param audioSource the recording source (also referred to as capture preset).
     *    See {@link MediaRecorder.AudioSource} for the capture preset definitions.
     * @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
     *   rate that is guaranteed to work on all devices, but other rates such as 22050,
     *   16000, and 11025 may work on some devices.
@@ -211,24 +215,89 @@ public class AudioRecord
    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes)
    throws IllegalArgumentException {
        this((new AudioAttributes.Builder())
                    .setInternalCapturePreset(audioSource)
                    .build(),
                (new AudioFormat.Builder())
                    .setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,
                                        true/*allow legacy configurations*/))
                    .setEncoding(audioFormat)
                    .setSampleRate(sampleRateInHz)
                    .build(),
                bufferSizeInBytes,
                AudioManager.AUDIO_SESSION_ID_GENERATE);
    }

    /**
     * @hide
     * CANDIDATE FOR PUBLIC API
     * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
     * @param attributes a non-null {@link AudioAttributes} instance. Use
     *     {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the capture
     *     preset for this instance.
     * @param format a non-null {@link AudioFormat} instance describing the format of the data
     *     that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
     *     configuring the audio format parameters such as encoding, channel mask and sample rate.
     * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
     *   to during the recording. New audio data can be read from this buffer in smaller chunks
     *   than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
     *   required buffer size for the successful creation of an AudioRecord instance. Using values
     *   smaller than getMinBufferSize() will result in an initialization failure.
     * @param sessionId ID of audio session the AudioRecord must be attached to, or
     *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
     *   time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
     *   construction.
     * @throws IllegalArgumentException
     */
    public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int sessionId) throws IllegalArgumentException {
        mRecordingState = RECORDSTATE_STOPPED;

        if (attributes == null) {
            throw new IllegalArgumentException("Illegal null AudioAttributes");
        }
        if (format == null) {
            throw new IllegalArgumentException("Illegal null AudioFormat");
        }

        // remember which looper is associated with the AudioRecord instanciation
        if ((mInitializationLooper = Looper.myLooper()) == null) {
            mInitializationLooper = Looper.getMainLooper();
        }

        audioParamCheck(audioSource, sampleRateInHz, channelConfig, audioFormat);
        mAudioAttributes = attributes;

        int rate = 0;
        if ((format.getPropertySetMask()
                & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
        {
            rate = format.getSampleRate();
        } else {
            rate = AudioSystem.getPrimaryOutputSamplingRate();
            if (rate <= 0) {
                rate = 44100;
            }
        }

        int encoding = AudioFormat.ENCODING_DEFAULT;
        if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0)
        {
            encoding = format.getEncoding();
        }

        audioParamCheck(attributes.getCapturePreset(), rate, encoding);

        mChannelCount = AudioFormat.channelCountFromInChannelMask(format.getChannelMask());
        mChannelMask = getChannelMaskFromLegacyConfig(format.getChannelMask(), false);

        audioBuffSizeCheck(bufferSizeInBytes);

        // native initialization
        int[] session = new int[1];
        session[0] = AudioSystem.AUDIO_SESSION_ALLOCATE;
        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),
                mRecordSource, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes,
                mAudioAttributes, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes,
                session);
        if (initResult != SUCCESS) {
            loge("Error code "+initResult+" when initializing native AudioRecord object.");
@@ -240,17 +309,42 @@ public class AudioRecord
        mState = STATE_INITIALIZED;
    }


    // Convenience method for the constructor's parameter checks.
    // This and audioBuffSizeCheck are where constructor IllegalArgumentException-s are thrown
    // This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
    // IllegalArgumentException-s are thrown
    private static int getChannelMaskFromLegacyConfig(int inChannelConfig,
            boolean allowLegacyConfig) {
        int mask;
        switch (inChannelConfig) {
        case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
        case AudioFormat.CHANNEL_IN_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            mask = AudioFormat.CHANNEL_IN_MONO;
            break;
        case AudioFormat.CHANNEL_IN_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
            mask = AudioFormat.CHANNEL_IN_STEREO;
            break;
        case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
            mask = inChannelConfig;
            break;
        default:
            throw new IllegalArgumentException("Unsupported channel configuration.");
        }

        if (!allowLegacyConfig && ((inChannelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO)
                || (inChannelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO))) {
            // only happens with the constructor that uses AudioAttributes and AudioFormat
            throw new IllegalArgumentException("Unsupported deprecated configuration.");
        }

        return mask;
    }
    // postconditions:
    //    mRecordSource is valid
    //    mChannelCount is valid
    //    mChannelMask is valid
    //    mAudioFormat is valid
    //    mSampleRate is valid
    private void audioParamCheck(int audioSource, int sampleRateInHz,
                                 int channelConfig, int audioFormat)
    private void audioParamCheck(int audioSource, int sampleRateInHz, int audioFormat)
            throws IllegalArgumentException {

        //--------------
@@ -270,28 +364,6 @@ public class AudioRecord
        }
        mSampleRate = sampleRateInHz;

        //--------------
        // channel config
        switch (channelConfig) {
        case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
        case AudioFormat.CHANNEL_IN_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            mChannelCount = 1;
            mChannelMask = AudioFormat.CHANNEL_IN_MONO;
            break;
        case AudioFormat.CHANNEL_IN_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
            mChannelCount = 2;
            mChannelMask = AudioFormat.CHANNEL_IN_STEREO;
            break;
        case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
            mChannelCount = 2;
            mChannelMask = channelConfig;
            break;
        default:
            throw new IllegalArgumentException("Unsupported channel configuration.");
        }

        //--------------
        // audio format
        switch (audioFormat) {
@@ -804,7 +876,8 @@ public class AudioRecord
    //--------------------

    private native final int native_setup(Object audiorecord_this,
            int recordSource, int sampleRate, int channelMask, int audioFormat,
            Object /*AudioAttributes*/ attributes,
            int sampleRate, int channelMask, int audioFormat,
            int buffSizeInBytes, int[] sessionId);

    private native final void native_finalize();
Loading