From a1d80e3b1d210c60c6881a55ed39a4077ff66080 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 18 Jun 2014 08:18:41 -0700 Subject: [PATCH] AudioTrack Java constructor with AudioAttributes and AudioFormat Change-Id: I82758a4231b8dc0b8d8e72acf3c896a289c28f60 --- core/jni/android_media_AudioTrack.cpp | 143 +++++++++++++----- media/java/android/media/AudioAttributes.java | 8 + media/java/android/media/AudioFormat.java | 16 ++ media/java/android/media/AudioSystem.java | 4 +- media/java/android/media/AudioTrack.java | 84 +++++++--- 5 files changed, 197 insertions(+), 58 deletions(-) diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 264a9ae6a9bd0..677c230b2f286 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -40,14 +40,23 @@ using namespace android; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioTrack"; +static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes"; -struct fields_t { +struct audio_track_fields_t { // these fields provide access from C++ to the... jmethodID postNativeEventInJava; //... event post callback method jfieldID nativeTrackInJavaObj; // stores in Java the native AudioTrack object jfieldID jniData; // stores in Java additional resources used by the native AudioTrack + jfieldID fieldStreamType; // ... mStreamType field in the AudioTrack Java object }; -static fields_t javaAudioTrackFields; +struct audio_attributes_fields_t { + jfieldID fieldUsage; // AudioAttributes.mUsage + jfieldID fieldContentType; // AudioAttributes.mContentType + jfieldID fieldFlags; // AudioAttributes.mFlags + jfieldID fieldTags; // AudioAttributes.mTags +}; +static audio_track_fields_t javaAudioTrackFields; +static audio_attributes_fields_t javaAudioAttrFields; struct audiotrack_callback_cookie { jclass audioTrack_class; @@ -66,12 +75,10 @@ class AudioTrackJniStorage { sp mMemHeap; sp mMemBase; audiotrack_callback_cookie mCallbackData; - audio_stream_type_t mStreamType; AudioTrackJniStorage() { mCallbackData.audioTrack_class = 0; mCallbackData.audioTrack_ref = 0; - mStreamType = AUDIO_STREAM_DEFAULT; } ~AudioTrackJniStorage() { @@ -174,16 +181,21 @@ static sp setAudioTrack(JNIEnv* env, jobject thiz, const spSetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (jlong)at.get()); return old; } - // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jint streamType, jint sampleRateInHertz, jint javaChannelMask, - jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) -{ + jobject jaa, + jint sampleRateInHertz, jint javaChannelMask, + jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) { + ALOGV("sampleRate=%d, audioFormat(from Java)=%d, channel mask=%x, buffSize=%d", sampleRateInHertz, audioFormat, javaChannelMask, buffSizeInBytes); + if (jaa == 0) { + ALOGE("Error creating AudioTrack: invalid audio attributes"); + return (jint) AUDIO_JAVA_ERROR; + } + // Java channel masks don't map directly to the native definition, but it's a simple shift // to skip the two deprecated channel configurations "default" and "mono". audio_channel_mask_t nativeChannelMask = ((uint32_t)javaChannelMask) >> 2; @@ -195,9 +207,6 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, uint32_t channelCount = popcount(nativeChannelMask); - // stream type already checked in Java - audio_stream_type_t atStreamType = (audio_stream_type_t) streamType; - // check the format. // This function was called from Java, so we compare the format against the Java constants audio_format_t format = audioFormatToNative(audioFormat); @@ -251,10 +260,25 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, // create the native AudioTrack object sp lpTrack = new AudioTrack(); + 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 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->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage); + paa->content_type = + (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType); + paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags); + + ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s", + paa->usage, paa->content_type, paa->flags, paa->tags); + // initialize the callback information: // this data will be passed with every AudioTrack callback AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage(); - lpJniStorage->mStreamType = atStreamType; lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); @@ -266,17 +290,21 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, case MODE_STREAM: status = lpTrack->set( - atStreamType,// stream type - sampleRateInHertz, - format,// word length, PCM - nativeChannelMask, - frameCount, - audio_is_linear_pcm(format) ? AUDIO_OUTPUT_FLAG_NONE : AUDIO_OUTPUT_FLAG_DIRECT, - audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user) - 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack - 0,// shared mem - true,// thread can call Java - sessionId);// audio session ID + AUDIO_STREAM_DEFAULT,// stream type + sampleRateInHertz, + format,// word length, PCM + nativeChannelMask, + frameCount, + AUDIO_OUTPUT_FLAG_NONE, + audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user) + 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack + 0,// shared mem + true,// thread can call Java + sessionId,// audio session ID + AudioTrack::TRANSFER_DEFAULT, // default transfer mode + NULL, // default offloadInfo + -1, -1, // default uid, pid values + paa); break; case MODE_STATIC: @@ -288,17 +316,21 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, } status = lpTrack->set( - atStreamType,// stream type - sampleRateInHertz, - format,// word length, PCM - nativeChannelMask, - frameCount, - AUDIO_OUTPUT_FLAG_NONE, - audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)); - 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack - lpJniStorage->mMemBase,// shared mem - true,// thread can call Java - sessionId);// audio session ID + AUDIO_STREAM_DEFAULT,// stream type + sampleRateInHertz, + format,// word length, PCM + nativeChannelMask, + frameCount, + AUDIO_OUTPUT_FLAG_NONE, + audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)); + 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack + lpJniStorage->mMemBase,// shared mem + true,// thread can call Java + sessionId,// audio session ID + AudioTrack::TRANSFER_DEFAULT, // default transfer mode + NULL, // default offloadInfo + -1, -1, // default uid, pid values + paa); break; default: @@ -333,10 +365,21 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, //ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage); env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage); + // since we had audio attributes, the stream type was derived from them during the + // creation of the native AudioTrack: push the same value to the Java object + env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType()); + // audio attributes were copied in AudioTrack creation + free(paa); + paa = NULL; + + return (jint) AUDIO_JAVA_SUCCESS; // failures: native_init_failure: + if (paa != NULL) { + free(paa); + } if (nSession != NULL) { env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); } @@ -948,7 +991,7 @@ static JNINativeMethod gMethods[] = { {"native_stop", "()V", (void *)android_media_AudioTrack_stop}, {"native_pause", "()V", (void *)android_media_AudioTrack_pause}, {"native_flush", "()V", (void *)android_media_AudioTrack_flush}, - {"native_setup", "(Ljava/lang/Object;IIIIII[I)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;IIIII[I)I", (void *)android_media_AudioTrack_setup}, {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize}, {"native_release", "()V", (void *)android_media_AudioTrack_release}, @@ -992,6 +1035,7 @@ static JNINativeMethod gMethods[] = { #define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" #define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME "mNativeTrackInJavaObj" #define JAVA_JNIDATA_FIELD_NAME "mJniData" +#define JAVA_STREAMTYPE_FIELD_NAME "mStreamType" // ---------------------------------------------------------------------------- // preconditions: @@ -1041,7 +1085,7 @@ int register_android_media_AudioTrack(JNIEnv *env) ALOGE("Can't find AudioTrack.%s", JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME); return -1; } - // jniData; + // jniData javaAudioTrackFields.jniData = env->GetFieldID( audioTrackClass, JAVA_JNIDATA_FIELD_NAME, "J"); @@ -1049,6 +1093,33 @@ int register_android_media_AudioTrack(JNIEnv *env) ALOGE("Can't find AudioTrack.%s", JAVA_JNIDATA_FIELD_NAME); return -1; } + // fieldStreamType + javaAudioTrackFields.fieldStreamType = env->GetFieldID(audioTrackClass, + JAVA_STREAMTYPE_FIELD_NAME, "I"); + if (javaAudioTrackFields.fieldStreamType == NULL) { + ALOGE("Can't find AudioTrack.%s", JAVA_STREAMTYPE_FIELD_NAME); + 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.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I"); + javaAudioAttrFields.fieldContentType + = env->GetFieldID(audioAttributesClassRef, "mContentType", "I"); + javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I"); + javaAudioAttrFields.fieldTags = env->GetFieldID(audioAttributesClassRef, "mFormattedTags", + "Ljava/lang/String;"); + env->DeleteGlobalRef(audioAttributesClassRef); + if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL + || javaAudioAttrFields.fieldFlags == NULL || javaAudioAttrFields.fieldTags == NULL) { + ALOGE("Can't initialize AudioAttributes fields"); + return -1; + } return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 394e437fdb800..57c66da632c43 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -25,6 +25,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; /** @@ -154,6 +155,7 @@ public final class AudioAttributes implements Parcelable { private int mContentType = CONTENT_TYPE_UNKNOWN; private int mFlags = 0x0; private HashSet mTags; + private String mFormattedTags; private AudioAttributes() { } @@ -241,6 +243,12 @@ public final class AudioAttributes implements Parcelable { aa.mUsage = mUsage; aa.mFlags = mFlags; aa.mTags = (HashSet) mTags.clone(); + final Iterator tagIterator = mTags.iterator(); + String allTagsInOne = new String(); + while (tagIterator.hasNext()) { + allTagsInOne += tagIterator.next() + ";"; + } + aa.mFormattedTags = allTagsInOne; return aa; } diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index bd2be1b94f370..a471e831a7291 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -227,6 +227,22 @@ public class AudioFormat { private int mChannelMask; private int mPropertySetMask; + int getEncoding() { + return mEncoding; + } + + int getSampleRate() { + return mSampleRate; + } + + int getChannelMask() { + return mChannelMask; + } + + int getPropertySetMask() { + return mPropertySetMask; + } + /** * @hide CANDIDATE FOR PUBLIC API * Builder class for {@link AudioFormat} objects. diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 63ed10c3fcf57..d3e0f4829faaa 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -28,11 +28,13 @@ import java.util.ArrayList; */ public class AudioSystem { - /* These values must be kept in sync with AudioSystem.h */ + /* These values must be kept in sync with system/audio.h */ /* * If these are modified, please also update Settings.System.VOLUME_SETTINGS * and attrs.xml and AudioManager.java. */ + /* The default audio stream */ + public static final int STREAM_DEFAULT = -1; /* The audio stream for phone calls */ public static final int STREAM_VOICE_CALL = 0; /* The audio stream for system sounds */ diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 3a72833db2c96..3f79e9adaa7d5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -21,6 +21,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.NioUtils; +import java.util.Iterator; +import java.util.Set; import android.annotation.IntDef; import android.app.ActivityThread; @@ -233,6 +235,8 @@ public class AudioTrack * {@link AudioManager#STREAM_DTMF}. */ private int mStreamType = AudioManager.STREAM_MUSIC; + + private final AudioAttributes mAttributes; /** * The way audio is consumed by the audio sink, streaming or static. */ @@ -349,21 +353,69 @@ public class AudioTrack int bufferSizeInBytes, int mode, int sessionId) throws IllegalArgumentException { // mState already == STATE_UNINITIALIZED + this((new AudioAttributes.Builder()) + .setLegacyStreamType(streamType) + .build(), + (new AudioFormat.Builder()) + .setChannelMask(channelConfig) + .setEncoding(audioFormat) + .setSampleRate(sampleRateInHz) + .build(), + bufferSizeInBytes, + mode, sessionId); + } + + /** + * @hide + * CANDIDATE FOR PUBLIC API + * Constructor with AudioAttributes and AudioFormat + * @param aa + * @param format + * @param bufferSizeInBytes + * @param mode + * @param sessionId + */ + public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, + int mode, int sessionId) + throws IllegalArgumentException { + // mState already == STATE_UNINITIALIZED // remember which looper is associated with the AudioTrack instantiation Looper looper; if ((looper = Looper.myLooper()) == null) { looper = Looper.getMainLooper(); } - mInitializationLooper = looper; - audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode); + 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 channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT; + if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) + { + channelMask = format.getChannelMask(); + } + int encoding = AudioFormat.ENCODING_DEFAULT; + if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0) { + encoding = format.getEncoding(); + } + audioParamCheck(rate, channelMask, encoding, mode); + mStreamType = AudioSystem.STREAM_DEFAULT; audioBuffSizeCheck(bufferSizeInBytes); + mInitializationLooper = looper; IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); mAppOps = IAppOpsService.Stub.asInterface(b); + mAttributes = (new AudioAttributes.Builder(attributes).build()); + if (sessionId < 0) { throw new IllegalArgumentException("Invalid audio session ID: "+sessionId); } @@ -371,8 +423,8 @@ public class AudioTrack int[] session = new int[1]; session[0] = sessionId; // native initialization - int initResult = native_setup(new WeakReference(this), - mStreamType, mSampleRate, mChannels, mAudioFormat, + int initResult = native_setup(new WeakReference(this), mAttributes, + mSampleRate, mChannels, mAudioFormat, mNativeBufferSizeInBytes, mDataLoadMode, session); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing AudioTrack."); @@ -401,27 +453,13 @@ public class AudioTrack // Convenience method for the constructor's parameter checks. // This is where constructor IllegalArgumentException-s are thrown // postconditions: - // mStreamType is valid // mChannelCount is valid // mChannels is valid // mAudioFormat is valid // mSampleRate is valid // mDataLoadMode is valid - private void audioParamCheck(int streamType, int sampleRateInHz, + private void audioParamCheck(int sampleRateInHz, int channelConfig, int audioFormat, int mode) { - - //-------------- - // stream type - if( (streamType != AudioManager.STREAM_ALARM) && (streamType != AudioManager.STREAM_MUSIC) - && (streamType != AudioManager.STREAM_RING) && (streamType != AudioManager.STREAM_SYSTEM) - && (streamType != AudioManager.STREAM_VOICE_CALL) - && (streamType != AudioManager.STREAM_NOTIFICATION) - && (streamType != AudioManager.STREAM_BLUETOOTH_SCO) - && (streamType != AudioManager.STREAM_DTMF)) { - throw new IllegalArgumentException("Invalid stream type."); - } - mStreamType = streamType; - //-------------- // sample rate, note these values are subject to change if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { @@ -1559,8 +1597,12 @@ public class AudioTrack // Native methods called from the Java side //-------------------- - private native final int native_setup(Object audiotrack_this, - int streamType, int sampleRate, int channelMask, int audioFormat, + // post-condition: mStreamType is overwritten with a value + // that reflects the audio attributes (e.g. an AudioAttributes object with a usage of + // AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC + private native final int native_setup(Object /*WeakReference*/ audiotrack_this, + Object /*AudioAttributes*/ attributes, + int sampleRate, int channelMask, int audioFormat, int buffSizeInBytes, int mode, int[] sessionId); private native final void native_finalize();