diff options
| -rw-r--r-- | api/current.txt | 7 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 165 | ||||
| -rw-r--r-- | media/jni/android_media_MediaRecorder.cpp | 54 |
3 files changed, 224 insertions, 2 deletions
diff --git a/api/current.txt b/api/current.txt index 8329aca26b2a..00a945294f09 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23256,16 +23256,20 @@ package android.media { field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1 } - public class MediaRecorder { + public class MediaRecorder implements android.media.AudioRouting { ctor public MediaRecorder(); + method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method protected void finalize(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; method public android.os.PersistableBundle getMetrics(); + method public android.media.AudioDeviceInfo getPreferredDevice(); + method public android.media.AudioDeviceInfo getRoutedDevice(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void release(); + method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method public void reset(); method public void resume() throws java.lang.IllegalStateException; method public void setAudioChannels(int); @@ -23288,6 +23292,7 @@ package android.media { method public void setOutputFile(java.io.File); method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException; method public void setOutputFormat(int) throws java.lang.IllegalStateException; + method public boolean setPreferredDevice(android.media.AudioDeviceInfo); method public void setPreviewDisplay(android.view.Surface); method public void setProfile(android.media.CamcorderProfile); method public void setVideoEncoder(int) throws java.lang.IllegalStateException; diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 7678490492b6..3c49b80b4b5e 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; +import android.util.ArrayMap; import android.util.Log; import android.view.Surface; @@ -34,6 +35,8 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.lang.ref.WeakReference; +import com.android.internal.annotations.GuardedBy; + /** * Used to record audio and video. The recording control is based on a * simple state machine (see below). @@ -76,7 +79,7 @@ import java.lang.ref.WeakReference; * <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p> * </div> */ -public class MediaRecorder +public class MediaRecorder implements AudioRouting { static { System.loadLibrary("media_jni"); @@ -1243,6 +1246,7 @@ public class MediaRecorder private static final int MEDIA_RECORDER_TRACK_EVENT_INFO = 101; private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END = 1000; + private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED = 10000; @Override public void handleMessage(Message msg) { @@ -1265,6 +1269,16 @@ public class MediaRecorder return; + case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED: + AudioManager.resetAudioPortGeneration(); + synchronized (mRoutingChangeListeners) { + for (NativeRoutingEventHandlerDelegate delegate + : mRoutingChangeListeners.values()) { + delegate.notifyClient(); + } + } + return; + default: Log.e(TAG, "Unknown message type " + msg.what); return; @@ -1272,6 +1286,155 @@ public class MediaRecorder } } + //-------------------------------------------------------------------------- + // Explicit Routing + //-------------------- + private AudioDeviceInfo mPreferredDevice = null; + + /** + * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route + * the input from this MediaRecorder. + * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source. + * If deviceInfo is null, default routing is restored. + * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and + * does not correspond to a valid audio input device. + */ + @Override + public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) { + if (deviceInfo != null && !deviceInfo.isSource()) { + return false; + } + int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; + boolean status = native_setInputDevice(preferredDeviceId); + if (status == true) { + synchronized (this) { + mPreferredDevice = deviceInfo; + } + } + return status; + } + + /** + * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this + * is not guaranteed to correspond to the actual device being used for recording. + */ + @Override + public AudioDeviceInfo getPreferredDevice() { + synchronized (this) { + return mPreferredDevice; + } + } + + /** + * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder + * Note: The query is only valid if the MediaRecorder is currently recording. + * If the recorder is not recording, the returned device can be null or correspond to previously + * selected device when the recorder was last active. + */ + @Override + public AudioDeviceInfo getRoutedDevice() { + int deviceId = native_getRoutedDeviceId(); + if (deviceId == 0) { + return null; + } + AudioDeviceInfo[] devices = + AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS); + for (int i = 0; i < devices.length; i++) { + if (devices[i].getId() == deviceId) { + return devices[i]; + } + } + return null; + } + + /* + * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler. + */ + private void enableNativeRoutingCallbacksLocked(boolean enabled) { + if (mRoutingChangeListeners.size() == 0) { + native_enableDeviceCallback(enabled); + } + } + + /** + * The list of AudioRouting.OnRoutingChangedListener interfaces added (with + * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)} + * by an app to receive (re)routing notifications. + */ + @GuardedBy("mRoutingChangeListeners") + private ArrayMap<AudioRouting.OnRoutingChangedListener, + NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>(); + + /** + * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing + * changes on this MediaRecorder. + * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive + * notifications of rerouting events. + * @param handler Specifies the {@link Handler} object for the thread on which to execute + * the callback. If <code>null</code>, the handler on the main looper will be used. + */ + @Override + public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, + Handler handler) { + synchronized (mRoutingChangeListeners) { + if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { + enableNativeRoutingCallbacksLocked(true); + mRoutingChangeListeners.put( + listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + } + } + } + + /** + * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added + * to receive rerouting notifications. + * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface + * to remove. + */ + @Override + public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) { + synchronized (mRoutingChangeListeners) { + if (mRoutingChangeListeners.containsKey(listener)) { + mRoutingChangeListeners.remove(listener); + enableNativeRoutingCallbacksLocked(false); + } + } + } + + /** + * Helper class to handle the forwarding of native events to the appropriate listener + * (potentially) handled in a different thread + */ + private class NativeRoutingEventHandlerDelegate { + private MediaRecorder mMediaRecorder; + private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener; + private Handler mHandler; + + NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder, + final AudioRouting.OnRoutingChangedListener listener, Handler handler) { + mMediaRecorder = mediaRecorder; + mOnRoutingChangedListener = listener; + mHandler = handler != null ? handler : mEventHandler; + } + + void notifyClient() { + if (mHandler != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mOnRoutingChangedListener != null) { + mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder); + } + } + }); + } + } + } + + private native final boolean native_setInputDevice(int deviceId); + private native final int native_getRoutedDeviceId(); + private native final void native_enableDeviceCallback(boolean enabled); + /** * Called from native code when an interesting event happens. This method * just uses the EventHandler system to post the event back to the main app thread. diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 497684c61f1e..d2bc1743654f 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -657,6 +657,56 @@ android_media_MediaRecorder_native_getMetrics(JNIEnv *env, jobject thiz) return mybundle; } + +static jboolean +android_media_MediaRecorder_setInputDevice(JNIEnv *env, jobject thiz, jint device_id) +{ + ALOGV("android_media_MediaRecorder_setInputDevice"); + + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + if (mr == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + + if (process_media_recorder_call(env, mr->setInputDevice(device_id), + "java/lang/RuntimeException", "setInputDevice failed.")) { + return false; + } + return true; +} + +static jint +android_media_MediaRecorder_getRoutedDeviceId(JNIEnv *env, jobject thiz) +{ + ALOGV("android_media_MediaRecorder_getRoutedDeviceId"); + + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + if (mr == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return AUDIO_PORT_HANDLE_NONE; + } + + audio_port_handle_t deviceId; + process_media_recorder_call(env, mr->getRoutedDeviceId(&deviceId), + "java/lang/RuntimeException", "getRoutedDeviceId failed."); + return (jint) deviceId; +} + +static void +android_media_MediaRecorder_enableDeviceCallback(JNIEnv *env, jobject thiz, jboolean enabled) +{ + ALOGV("android_media_MediaRecorder_enableDeviceCallback %d", enabled); + + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + if (mr == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + process_media_recorder_call(env, mr->enableAudioDeviceCallback(enabled), + "java/lang/RuntimeException", "enableDeviceCallback failed."); +} // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -689,6 +739,10 @@ static const JNINativeMethod gMethods[] = { {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface }, {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics}, + + {"native_setInputDevice", "(I)Z", (void *)android_media_MediaRecorder_setInputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaRecorder_getRoutedDeviceId}, + {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaRecorder_enableDeviceCallback}, }; // This function only registers the native methods, and is called from |