OSDN Git Service

Implement audio routing callbacks
authorEric Laurent <elaurent@google.com>
Fri, 1 May 2015 18:37:49 +0000 (11:37 -0700)
committerJean-Michel Trivi <jmtrivi@google.com>
Wed, 6 May 2015 17:03:50 +0000 (17:03 +0000)
Implement JNI for AudioTrack and AudioRecord routing
callbacks:
- Added files core/jni/android_media_DeviceCallback.cpp/.h
for JNI callback implementation used by both AudioTrack
and AudioRecord.
- Made AudioManager AudioPort and AudioPatch methods static
in order to call them without context.

Also added IO handle information to AudioMixPort.

Change-Id: Icee182aa68310d4b12fd1469f48e78110889acf1

14 files changed:
core/jni/Android.mk
core/jni/android_media_AudioRecord.cpp
core/jni/android_media_AudioSystem.cpp
core/jni/android_media_AudioTrack.cpp
core/jni/android_media_DeviceCallback.cpp [new file with mode: 0644]
core/jni/android_media_DeviceCallback.h [new file with mode: 0644]
media/java/android/media/AudioDevicePort.java
media/java/android/media/AudioDevicesManager.java
media/java/android/media/AudioManager.java
media/java/android/media/AudioMixPort.java
media/java/android/media/AudioPort.java
media/java/android/media/AudioRecord.java
media/java/android/media/AudioSystem.java
media/java/android/media/AudioTrack.java

index bbdd860..5448214 100644 (file)
@@ -132,6 +132,7 @@ LOCAL_SRC_FILES:= \
     android_media_AudioRecord.cpp \
     android_media_AudioSystem.cpp \
     android_media_AudioTrack.cpp \
+    android_media_DeviceCallback.cpp \
     android_media_JetPlayer.cpp \
     android_media_RemoteDisplay.cpp \
     android_media_ToneGenerator.cpp \
index 6afb226..baa180b 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "android_media_AudioFormat.h"
 #include "android_media_AudioErrors.h"
+#include "android_media_DeviceCallback.h"
 
 // ----------------------------------------------------------------------------
 
@@ -44,6 +45,7 @@ struct audio_record_fields_t {
     jmethodID postNativeEventInJava; //... event post callback method
     jfieldID  nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object
     jfieldID  nativeCallbackCookie;    // provides access to the AudioRecord callback data
+    jfieldID  nativeDeviceCallback;    // provides access to the JNIDeviceCallback instance
 };
 struct audio_attributes_fields_t {
     jfieldID  fieldRecSource;    // AudioAttributes.mSource
@@ -120,6 +122,33 @@ static void recorderCallback(int event, void* user, void *info) {
     }
 }
 
+static sp<JNIDeviceCallback> getJniDeviceCallback(JNIEnv* env, jobject thiz)
+{
+    Mutex::Autolock l(sLock);
+    JNIDeviceCallback* const cb =
+            (JNIDeviceCallback*)env->GetLongField(thiz,
+                                                  javaAudioRecordFields.nativeDeviceCallback);
+    return sp<JNIDeviceCallback>(cb);
+}
+
+static sp<JNIDeviceCallback> setJniDeviceCallback(JNIEnv* env,
+                                                  jobject thiz,
+                                                  const sp<JNIDeviceCallback>& cb)
+{
+    Mutex::Autolock l(sLock);
+    sp<JNIDeviceCallback> old =
+            (JNIDeviceCallback*)env->GetLongField(thiz,
+                                                  javaAudioRecordFields.nativeDeviceCallback);
+    if (cb.get()) {
+        cb->incStrong((void*)setJniDeviceCallback);
+    }
+    if (old != 0) {
+        old->decStrong((void*)setJniDeviceCallback);
+    }
+    env->SetLongField(thiz, javaAudioRecordFields.nativeDeviceCallback, (jlong)cb.get());
+    return old;
+}
+
 // ----------------------------------------------------------------------------
 static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz)
 {
@@ -592,9 +621,63 @@ static jboolean android_media_AudioRecord_setInputDevice(
         JNIEnv *env,  jobject thiz, jint device_id) {
 
     sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
+    if (lpRecorder == 0) {
+        return 0;
+    }
     return lpRecorder->setInputDevice(device_id) == NO_ERROR;
 }
 
+static jint android_media_AudioRecord_getRoutedDeviceId(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
+    if (lpRecorder == 0) {
+        return 0;
+    }
+    return (jint)lpRecorder->getRoutedDeviceId();
+}
+
+static void android_media_AudioRecord_enableDeviceCallback(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
+    if (lpRecorder == 0) {
+        return;
+    }
+    sp<JNIDeviceCallback> cb = getJniDeviceCallback(env, thiz);
+    if (cb != 0) {
+        return;
+    }
+    audiorecord_callback_cookie *cookie =
+            (audiorecord_callback_cookie *)env->GetLongField(thiz,
+                                                     javaAudioRecordFields.nativeCallbackCookie);
+    if (cookie == NULL) {
+        return;
+    }
+
+    cb = new JNIDeviceCallback(env, thiz, cookie->audioRecord_ref,
+                               javaAudioRecordFields.postNativeEventInJava);
+    status_t status = lpRecorder->addAudioDeviceCallback(cb);
+    if (status == NO_ERROR) {
+        setJniDeviceCallback(env, thiz, cb);
+    }
+}
+
+static void android_media_AudioRecord_disableDeviceCallback(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
+    if (lpRecorder == 0) {
+        return;
+    }
+    sp<JNIDeviceCallback> cb = setJniDeviceCallback(env, thiz, 0);
+    if (cb != 0) {
+        lpRecorder->removeAudioDeviceCallback(cb);
+    }
+}
+
+
+
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static JNINativeMethod gMethods[] = {
@@ -627,12 +710,17 @@ static JNINativeMethod gMethods[] = {
     {"native_get_min_buff_size",
                              "(III)I",   (void *)android_media_AudioRecord_get_min_buff_size},
     {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice},
+    {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId},
+    {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback},
+    {"native_disableDeviceCallback", "()V",
+                                        (void *)android_media_AudioRecord_disableDeviceCallback},
 };
 
 // field names found in android/media/AudioRecord.java
 #define JAVA_POSTEVENT_CALLBACK_NAME  "postEventFromNative"
 #define JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME  "mNativeRecorderInJavaObj"
 #define JAVA_NATIVECALLBACKINFO_FIELD_NAME       "mNativeCallbackCookie"
+#define JAVA_NATIVEDEVICECALLBACK_FIELD_NAME       "mNativeDeviceCallback"
 
 // ----------------------------------------------------------------------------
 int register_android_media_AudioRecord(JNIEnv *env)
@@ -640,6 +728,7 @@ int register_android_media_AudioRecord(JNIEnv *env)
     javaAudioRecordFields.postNativeEventInJava = NULL;
     javaAudioRecordFields.nativeRecorderInJavaObj = NULL;
     javaAudioRecordFields.nativeCallbackCookie = NULL;
+    javaAudioRecordFields.nativeDeviceCallback = NULL;
 
 
     // Get the AudioRecord class
@@ -657,6 +746,9 @@ int register_android_media_AudioRecord(JNIEnv *env)
     javaAudioRecordFields.nativeCallbackCookie = GetFieldIDOrDie(env,
             audioRecordClass, JAVA_NATIVECALLBACKINFO_FIELD_NAME, "J");
 
+    javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env,
+            audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J");
+
     // Get the AudioAttributes class and fields
     jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName);
     javaAudioAttrFields.fieldRecSource = GetFieldIDOrDie(env, audioAttrClass, "mSource", "I");
index 3655adc..eab5668 100644 (file)
@@ -937,7 +937,8 @@ static jint convertAudioPortFromNative(JNIEnv *env,
     } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
         ALOGV("convertAudioPortFromNative is a mix");
         *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor,
-                                     jHandle, nAudioPort->role, jDeviceName,
+                                     jHandle, nAudioPort->ext.mix.handle,
+                                     nAudioPort->role, jDeviceName,
                                      jSamplingRates, jChannelMasks,
                                      jFormats, jGains);
     } else {
@@ -1670,7 +1671,7 @@ int register_android_media_AudioSystem(JNIEnv *env)
     jclass audioMixPortClass = FindClassOrDie(env, "android/media/AudioMixPort");
     gAudioMixPortClass = MakeGlobalRefOrDie(env, audioMixPortClass);
     gAudioMixPortCstor = GetMethodIDOrDie(env, audioMixPortClass, "<init>",
-            "(Landroid/media/AudioHandle;ILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V");
+            "(Landroid/media/AudioHandle;IILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V");
 
     jclass audioGainClass = FindClassOrDie(env, "android/media/AudioGain");
     gAudioGainClass = MakeGlobalRefOrDie(env, audioGainClass);
index 26b82c5..662ecd3 100644 (file)
@@ -36,6 +36,7 @@
 #include "android_media_AudioFormat.h"
 #include "android_media_AudioErrors.h"
 #include "android_media_PlaybackSettings.h"
+#include "android_media_DeviceCallback.h"
 
 // ----------------------------------------------------------------------------
 
@@ -79,6 +80,7 @@ class AudioTrackJniStorage {
         sp<MemoryHeapBase>         mMemHeap;
         sp<MemoryBase>             mMemBase;
         audiotrack_callback_cookie mCallbackData;
+        sp<JNIDeviceCallback>      mDeviceCallback;
 
     AudioTrackJniStorage() {
         mCallbackData.audioTrack_class = 0;
@@ -977,6 +979,51 @@ static jboolean android_media_AudioTrack_setOutputDevice(
     return lpTrack->setOutputDevice(device_id) == NO_ERROR;
 }
 
+static jint android_media_AudioTrack_getRoutedDeviceId(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        return 0;
+    }
+    return (jint)lpTrack->getRoutedDeviceId();
+}
+
+static void android_media_AudioTrack_enableDeviceCallback(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        return;
+    }
+    AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(
+        thiz, javaAudioTrackFields.jniData);
+    if (pJniStorage == NULL || pJniStorage->mDeviceCallback != 0) {
+        return;
+    }
+    pJniStorage->mDeviceCallback =
+    new JNIDeviceCallback(env, thiz, pJniStorage->mCallbackData.audioTrack_ref,
+                          javaAudioTrackFields.postNativeEventInJava);
+    lpTrack->addAudioDeviceCallback(pJniStorage->mDeviceCallback);
+}
+
+static void android_media_AudioTrack_disableDeviceCallback(
+                JNIEnv *env,  jobject thiz) {
+
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        return;
+    }
+    AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(
+        thiz, javaAudioTrackFields.jniData);
+    if (pJniStorage == NULL || pJniStorage->mDeviceCallback == 0) {
+        return;
+    }
+    lpTrack->removeAudioDeviceCallback(pJniStorage->mDeviceCallback);
+    pJniStorage->mDeviceCallback.clear();
+}
+
+
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static JNINativeMethod gMethods[] = {
@@ -1030,6 +1077,9 @@ static JNINativeMethod gMethods[] = {
                              "(I)I",     (void *)android_media_AudioTrack_attachAuxEffect},
     {"native_setOutputDevice", "(I)Z",
                              (void *)android_media_AudioTrack_setOutputDevice},
+    {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId},
+    {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback},
+    {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback},
 };
 
 
diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp
new file mode 100644 (file)
index 0000000..e159373
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioDeviceCallback-JNI"
+
+#include <utils/Log.h>
+#include <JNIHelp.h>
+#include <JniConstants.h>
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+#include "android_media_DeviceCallback.h"
+
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+JNIDeviceCallback::JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz,
+                                     jmethodID postEventFromNative)
+{
+
+    // Hold onto the AudioTrack/AudioRecord class for use in calling the static method
+    // that posts events to the application thread.
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        return;
+    }
+    mClass = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the AudioTrack/AudioRecord object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    mObject  = env->NewGlobalRef(weak_thiz);
+
+    mPostEventFromNative = postEventFromNative;
+}
+
+JNIDeviceCallback::~JNIDeviceCallback()
+{
+    // remove global references
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        return;
+    }
+    env->DeleteGlobalRef(mObject);
+    env->DeleteGlobalRef(mClass);
+}
+
+void JNIDeviceCallback::onAudioDeviceUpdate(audio_io_handle_t audioIo,
+                                            audio_port_handle_t deviceId)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        return;
+    }
+
+    ALOGV("%s audioIo %d deviceId %d", __FUNCTION__, audioIo, deviceId);
+    env->CallStaticVoidMethod(mClass,
+                              mPostEventFromNative,
+                              mObject,
+                              AUDIO_NATIVE_EVENT_ROUTING_CHANGE, deviceId, 0, NULL);
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        env->ExceptionClear();
+    }
+}
+
diff --git a/core/jni/android_media_DeviceCallback.h b/core/jni/android_media_DeviceCallback.h
new file mode 100644 (file)
index 0000000..7ae788e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_DEVICE_CALLBACK_H
+#define ANDROID_MEDIA_DEVICE_CALLBACK_H
+
+#include <system/audio.h>
+#include <media/AudioSystem.h>
+
+namespace android {
+
+// keep in sync with AudioSystem.java
+#define AUDIO_NATIVE_EVENT_ROUTING_CHANGE      1000
+
+class JNIDeviceCallback: public AudioSystem::AudioDeviceCallback
+{
+public:
+    JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, jmethodID postEventFromNative);
+    ~JNIDeviceCallback();
+
+    virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo,
+                                     audio_port_handle_t deviceId);
+
+private:
+    void sendEvent(int event);
+
+    jclass      mClass;     // Reference to AudioTrack/AudioRecord class
+    jobject     mObject;    // Weak ref to AudioTrack/AudioRecord Java object to call on
+    jmethodID   mPostEventFromNative; // postEventFromNative method ID.
+};
+
+}; // namespace android
+
+#endif // ANDROID_MEDIA_DEVICE_CALLBACK_H
index 82da27d..c078260 100644 (file)
@@ -83,6 +83,16 @@ public class AudioDevicePort extends AudioPort {
         if (o == null || !(o instanceof AudioDevicePort)) {
             return false;
         }
+        AudioDevicePort other = (AudioDevicePort)o;
+        if (mType != other.type()) {
+            return false;
+        }
+        if (mAddress == null && other.address() != null) {
+            return false;
+        }
+        if (!mAddress.equals(other.address())) {
+            return false;
+        }
         return super.equals(o);
     }
 
index ca238d7..8b83c17 100644 (file)
@@ -96,7 +96,7 @@ public class AudioDevicesManager {
      * @param flags A set of bitflags specifying the criteria to test.
      * @see {@link LIST_DEVICES_OUTPUTS} and {@link LIST_DEVICES_INPUTS}
      **/
-    private boolean checkFlags(AudioDevicePort port, int flags) {
+    private static boolean checkFlags(AudioDevicePort port, int flags) {
         return port.role() == AudioPort.ROLE_SINK && (flags & LIST_DEVICES_OUTPUTS) != 0 ||
                port.role() == AudioPort.ROLE_SOURCE && (flags & LIST_DEVICES_INPUTS) != 0;
     }
@@ -110,8 +110,21 @@ public class AudioDevicesManager {
      * @return A (possibly zero-length) array of AudioDeviceInfo objects.
      */
     public AudioDeviceInfo[] listDevices(int flags) {
+        return listDevicesStatic(flags);
+    }
+
+    /**
+     * Generates a list of AudioDeviceInfo objects corresponding to the audio devices currently
+     * connected to the system and meeting the criteria specified in the <code>flags</code>
+     * parameter.
+     * @param flags A set of bitflags specifying the criteria to test.
+     * @see {@link LIST_DEVICES_OUTPUTS}, {@link LIST_DEVICES_INPUTS} and {@link LIST_DEVICES_ALL}.
+     * @return A (possibly zero-length) array of AudioDeviceInfo objects.
+     * @hide
+     */
+    public static AudioDeviceInfo[] listDevicesStatic(int flags) {
         ArrayList<AudioDevicePort> ports = new ArrayList<AudioDevicePort>();
-        int status = mAudioManager.listAudioDevicePorts(ports);
+        int status = AudioManager.listAudioDevicePorts(ports);
         if (status != AudioManager.SUCCESS) {
             // fail and bail!
             return new AudioDeviceInfo[0];
index eeac69a..8cd2688 100644 (file)
@@ -3381,7 +3381,7 @@ public class AudioManager {
      * @param ports An AudioPort ArrayList where the list will be returned.
      * @hide
      */
-    public int listAudioPorts(ArrayList<AudioPort> ports) {
+    public static int listAudioPorts(ArrayList<AudioPort> ports) {
         return updateAudioPortCache(ports, null);
     }
 
@@ -3390,7 +3390,7 @@ public class AudioManager {
      * @see listAudioPorts(ArrayList<AudioPort>)
      * @hide
      */
-    public int listAudioDevicePorts(ArrayList<AudioDevicePort> devices) {
+    public static int listAudioDevicePorts(ArrayList<AudioDevicePort> devices) {
         ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
         int status = updateAudioPortCache(ports, null);
         if (status == SUCCESS) {
@@ -3427,7 +3427,7 @@ public class AudioManager {
      *         patch[0] contains the newly created patch
      * @hide
      */
-    public int createAudioPatch(AudioPatch[] patch,
+    public static int createAudioPatch(AudioPatch[] patch,
                                  AudioPortConfig[] sources,
                                  AudioPortConfig[] sinks) {
         return AudioSystem.createAudioPatch(patch, sources, sinks);
@@ -3444,7 +3444,7 @@ public class AudioManager {
      *         - {@link #ERROR} if patch cannot be released for any other reason.
      * @hide
      */
-    public int releaseAudioPatch(AudioPatch patch) {
+    public static int releaseAudioPatch(AudioPatch patch) {
         return AudioSystem.releaseAudioPatch(patch);
     }
 
@@ -3453,7 +3453,7 @@ public class AudioManager {
      * @param patches An AudioPatch array where the list will be returned.
      * @hide
      */
-    public int listAudioPatches(ArrayList<AudioPatch> patches) {
+    public static int listAudioPatches(ArrayList<AudioPatch> patches) {
         return updateAudioPortCache(null, patches);
     }
 
@@ -3462,7 +3462,7 @@ public class AudioManager {
      * AudioGain.buildConfig()
      * @hide
      */
-    public int setAudioPortGain(AudioPort port, AudioGainConfig gain) {
+    public static int setAudioPortGain(AudioPort port, AudioGainConfig gain) {
         if (port == null || gain == null) {
             return ERROR_BAD_VALUE;
         }
index 9fac8d1..ab55c8d 100644 (file)
@@ -20,16 +20,21 @@ package android.media;
  * The AudioMixPort is a specialized type of AudioPort
  * describing an audio mix or stream at an input or output stream of the audio
  * framework.
+ * In addition to base audio port attributes, the mix descriptor contains:
+ * - the unique audio I/O handle assigned by AudioFlinger to this mix.
  * @see AudioPort
  * @hide
  */
 
 public class AudioMixPort extends AudioPort {
 
-    AudioMixPort(AudioHandle handle, int role, String deviceName,
+    private final int mIoHandle;
+
+    AudioMixPort(AudioHandle handle, int ioHandle, int role, String deviceName,
             int[] samplingRates, int[] channelMasks,
             int[] formats, AudioGain[] gains) {
         super(handle, role, deviceName, samplingRates, channelMasks, formats, gains);
+        mIoHandle = ioHandle;
     }
 
     /**
@@ -41,11 +46,23 @@ public class AudioMixPort extends AudioPort {
         return new AudioMixPortConfig(this, samplingRate, channelMask, format, gain);
     }
 
+    /**
+     * Get the device type (e.g AudioManager.DEVICE_OUT_SPEAKER)
+     */
+    public int ioHandle() {
+        return mIoHandle;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == null || !(o instanceof AudioMixPort)) {
             return false;
         }
+        AudioMixPort other = (AudioMixPort)o;
+        if (mIoHandle != other.ioHandle()) {
+            return false;
+        }
+
         return super.equals(o);
     }
 
index 88e784a..7328d7a 100644 (file)
@@ -93,6 +93,14 @@ public class AudioPort {
     }
 
     /**
+     * Get the system unique device ID.
+     */
+    public int id() {
+        return mHandle.id();
+    }
+
+
+    /**
      * Get the audio port role
      */
     public int role() {
index 472da02..11671d8 100644 (file)
@@ -116,11 +116,6 @@ public class AudioRecord
      */
     private static final int NATIVE_EVENT_NEW_POS = 3;
 
-    /**
-     * Event id denotes when the routing changes.
-     */
-    private final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
-
     private final static String TAG = "android.media.AudioRecord";
 
     /** @hide */
@@ -161,6 +156,12 @@ public class AudioRecord
     @SuppressWarnings("unused")
     private long mNativeCallbackCookie;
 
+    /**
+     * Accessed by native methods: provides access to the JNIDeviceCallback instance.
+     */
+    @SuppressWarnings("unused")
+    private long mNativeDeviceCallback;
+
 
     //---------------------------------------------------------
     // Member variables
@@ -1205,6 +1206,17 @@ public class AudioRecord
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioRecord.
      */
     public AudioDeviceInfo getRoutedDevice() {
+        int deviceId = native_getRoutedDeviceId();
+        if (deviceId == 0) {
+            return null;
+        }
+        AudioDeviceInfo[] devices =
+                AudioDevicesManager.listDevicesStatic(AudioDevicesManager.LIST_DEVICES_INPUTS);
+        for (int i = 0; i < devices.length; i++) {
+            if (devices[i].getId() == deviceId) {
+                return devices[i];
+            }
+        }
         return null;
     }
 
@@ -1224,6 +1236,9 @@ public class AudioRecord
             android.os.Handler handler) {
         if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
             synchronized (mRoutingChangeListeners) {
+                if (mRoutingChangeListeners.size() == 0) {
+                    native_enableDeviceCallback();
+                }
                 mRoutingChangeListeners.put(
                     listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
             }
@@ -1238,6 +1253,9 @@ public class AudioRecord
         synchronized (mRoutingChangeListeners) {
             if (mRoutingChangeListeners.containsKey(listener)) {
                 mRoutingChangeListeners.remove(listener);
+                if (mRoutingChangeListeners.size() == 0) {
+                    native_disableDeviceCallback();
+                }
             }
         }
     }
@@ -1271,7 +1289,7 @@ public class AudioRecord
                             return;
                         }
                         switch(msg.what) {
-                        case NATIVE_EVENT_ROUTING_CHANGE:
+                        case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
                             if (listener != null) {
                                 listener.onAudioRecordRouting(record);
                             }
@@ -1299,10 +1317,11 @@ public class AudioRecord
         synchronized (mRoutingChangeListeners) {
             values = mRoutingChangeListeners.values();
         }
+        AudioManager.resetAudioPortGeneration();
         for(NativeRoutingEventHandlerDelegate delegate : values) {
             Handler handler = delegate.getHandler();
             if (handler != null) {
-                handler.sendEmptyMessage(NATIVE_EVENT_ROUTING_CHANGE);
+                handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
             }
         }
     }
@@ -1341,10 +1360,14 @@ public class AudioRecord
             return false;
         }
 
-        mPreferredDevice = deviceInfo;
-        int preferredDeviceId = mPreferredDevice != null ? deviceInfo.getId() : 0;
-
-        return native_setInputDevice(preferredDeviceId);
+        int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+        boolean status = native_setInputDevice(preferredDeviceId);
+        if (status == true) {
+            synchronized (this) {
+                mPreferredDevice = deviceInfo;
+            }
+        }
+        return status;
     }
 
     /**
@@ -1352,7 +1375,9 @@ public class AudioRecord
      * is not guarenteed to correspond to the actual device being used for recording.
      */
     public AudioDeviceInfo getPreferredInputDevice() {
-        return mPreferredDevice;
+        synchronized (this) {
+            return mPreferredDevice;
+        }
     }
 
     //---------------------------------------------------------
@@ -1435,6 +1460,11 @@ public class AudioRecord
             return;
         }
 
+        if (what == AudioSystem.NATIVE_EVENT_ROUTING_CHANGE) {
+            recorder.broadcastRoutingChange();
+            return;
+        }
+
         if (recorder.mEventHandler != null) {
             Message m =
                 recorder.mEventHandler.obtainMessage(what, arg1, arg2, obj);
@@ -1486,7 +1516,9 @@ public class AudioRecord
             int sampleRateInHz, int channelCount, int audioFormat);
 
     private native final boolean native_setInputDevice(int deviceId);
-
+    private native final int native_getRoutedDeviceId();
+    private native final void native_enableDeviceCallback();
+    private native final void native_disableDeviceCallback();
 
     //---------------------------------------------------------
     // Utility methods
index 3dae543..ee12374 100644 (file)
@@ -721,5 +721,11 @@ public class AudioSystem
             (1 << STREAM_RING) |
             (1 << STREAM_NOTIFICATION) |
             (1 << STREAM_SYSTEM);
+
+    /**
+     * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes.
+     * Keep in sync with core/jni/android_media_DeviceCallback.h.
+     */
+    final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
 }
 
index cb05cc5..a66a1e5 100644 (file)
@@ -178,12 +178,6 @@ public class AudioTrack
      */
     private static final int NATIVE_EVENT_NEW_POS = 4;
 
-    /**
-     * Event id denotes when the routing changes.
-     */
-    private final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
-
-
     private final static String TAG = "android.media.AudioTrack";
 
 
@@ -2057,11 +2051,14 @@ public class AudioTrack
         if (deviceInfo != null && !deviceInfo.isSink()) {
             return false;
         }
-
-        mPreferredDevice = deviceInfo;
-        int preferredDeviceId = mPreferredDevice != null ? deviceInfo.getId() : 0;
-
-        return native_setOutputDevice(preferredDeviceId);
+        int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+        boolean status = native_setOutputDevice(preferredDeviceId);
+        if (status == true) {
+            synchronized (this) {
+                mPreferredDevice = deviceInfo;
+            }
+        }
+        return status;
     }
 
     /**
@@ -2069,7 +2066,9 @@ public class AudioTrack
      * is not guaranteed to correspond to the actual device being used for playback.
      */
     public AudioDeviceInfo getPreferredOutputDevice() {
-        return mPreferredDevice;
+        synchronized (this) {
+            return mPreferredDevice;
+        }
     }
 
     //--------------------------------------------------------------------------
@@ -2079,6 +2078,17 @@ public class AudioTrack
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack.
      */
     public AudioDeviceInfo getRoutedDevice() {
+        int deviceId = native_getRoutedDeviceId();
+        if (deviceId == 0) {
+            return null;
+        }
+        AudioDeviceInfo[] devices =
+                AudioDevicesManager.listDevicesStatic(AudioDevicesManager.LIST_DEVICES_OUTPUTS);
+        for (int i = 0; i < devices.length; i++) {
+            if (devices[i].getId() == deviceId) {
+                return devices[i];
+            }
+        }
         return null;
     }
 
@@ -2098,6 +2108,9 @@ public class AudioTrack
             android.os.Handler handler) {
         if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
             synchronized (mRoutingChangeListeners) {
+                if (mRoutingChangeListeners.size() == 0) {
+                    native_enableDeviceCallback();
+                }
                 mRoutingChangeListeners.put(
                     listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
             }
@@ -2113,6 +2126,9 @@ public class AudioTrack
             if (mRoutingChangeListeners.containsKey(listener)) {
                 mRoutingChangeListeners.remove(listener);
             }
+            if (mRoutingChangeListeners.size() == 0) {
+                native_disableDeviceCallback();
+            }
         }
     }
 
@@ -2124,10 +2140,11 @@ public class AudioTrack
         synchronized (mRoutingChangeListeners) {
             values = mRoutingChangeListeners.values();
         }
+        AudioManager.resetAudioPortGeneration();
         for(NativeRoutingEventHandlerDelegate delegate : values) {
             Handler handler = delegate.getHandler();
             if (handler != null) {
-                handler.sendEmptyMessage(NATIVE_EVENT_ROUTING_CHANGE);
+                handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
             }
         }
     }
@@ -2240,7 +2257,7 @@ public class AudioTrack
                             return;
                         }
                         switch(msg.what) {
-                        case NATIVE_EVENT_ROUTING_CHANGE:
+                        case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
                             if (listener != null) {
                                 listener.onAudioTrackRouting(track);
                             }
@@ -2273,6 +2290,10 @@ public class AudioTrack
             return;
         }
 
+        if (what == AudioSystem.NATIVE_EVENT_ROUTING_CHANGE) {
+            track.broadcastRoutingChange();
+            return;
+        }
         NativePositionEventHandlerDelegate delegate = track.mEventHandlerDelegate;
         if (delegate != null) {
             Handler handler = delegate.getHandler();
@@ -2281,7 +2302,6 @@ public class AudioTrack
                 handler.sendMessage(m);
             }
         }
-
     }
 
 
@@ -2362,6 +2382,9 @@ public class AudioTrack
     private native final int native_setAuxEffectSendLevel(float level);
 
     private native final boolean native_setOutputDevice(int deviceId);
+    private native final int native_getRoutedDeviceId();
+    private native final void native_enableDeviceCallback();
+    private native final void native_disableDeviceCallback();
 
     //---------------------------------------------------------
     // Utility methods