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

Commit fe3e6024 authored by jiabin's avatar jiabin
Browse files

Add AudioRouting interface in MediaRecorder.

Bug: 64038649
Test: Run cts in RoutingTest
      Switching input device when using MediaRecorder

Change-Id: I3f2a9f670565c6e55d612c1477a505fa3d096e3d
parent 684cae74
Loading
Loading
Loading
Loading
+6 −1
Original line number Original line Diff line number Diff line
@@ -23256,16 +23256,20 @@ package android.media {
    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
  }
  }
  public class MediaRecorder {
  public class MediaRecorder implements android.media.AudioRouting {
    ctor public MediaRecorder();
    ctor public MediaRecorder();
    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
    method protected void finalize();
    method protected void finalize();
    method public static final int getAudioSourceMax();
    method public static final int getAudioSourceMax();
    method public int getMaxAmplitude() throws java.lang.IllegalStateException;
    method public int getMaxAmplitude() throws java.lang.IllegalStateException;
    method public android.os.PersistableBundle getMetrics();
    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 android.view.Surface getSurface();
    method public void pause() throws java.lang.IllegalStateException;
    method public void pause() throws java.lang.IllegalStateException;
    method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
    method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
    method public void release();
    method public void release();
    method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
    method public void reset();
    method public void reset();
    method public void resume() throws java.lang.IllegalStateException;
    method public void resume() throws java.lang.IllegalStateException;
    method public void setAudioChannels(int);
    method public void setAudioChannels(int);
@@ -23288,6 +23292,7 @@ package android.media {
    method public void setOutputFile(java.io.File);
    method public void setOutputFile(java.io.File);
    method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException;
    method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException;
    method public void setOutputFormat(int) 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 setPreviewDisplay(android.view.Surface);
    method public void setProfile(android.media.CamcorderProfile);
    method public void setProfile(android.media.CamcorderProfile);
    method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
    method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
+164 −1
Original line number Original line Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PersistableBundle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Log;
import android.view.Surface;
import android.view.Surface;


@@ -34,6 +35,8 @@ import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.lang.ref.WeakReference;


import com.android.internal.annotations.GuardedBy;

/**
/**
 * Used to record audio and video. The recording control is based on a
 * Used to record audio and video. The recording control is based on a
 * simple state machine (see below).
 * 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>
 * <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p>
 * </div>
 * </div>
 */
 */
public class MediaRecorder
public class MediaRecorder implements AudioRouting
{
{
    static {
    static {
        System.loadLibrary("media_jni");
        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_INFO       = 101;
        private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END   = 1000;
        private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END   = 1000;


        private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED  = 10000;


        @Override
        @Override
        public void handleMessage(Message msg) {
        public void handleMessage(Message msg) {
@@ -1265,6 +1269,16 @@ public class MediaRecorder


                return;
                return;


            case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:
                AudioManager.resetAudioPortGeneration();
                synchronized (mRoutingChangeListeners) {
                    for (NativeRoutingEventHandlerDelegate delegate
                            : mRoutingChangeListeners.values()) {
                        delegate.notifyClient();
                    }
                }
                return;

            default:
            default:
                Log.e(TAG, "Unknown message type " + msg.what);
                Log.e(TAG, "Unknown message type " + msg.what);
                return;
                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
     * 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.
     * just uses the EventHandler system to post the event back to the main app thread.
+54 −0
Original line number Original line Diff line number Diff line
@@ -657,6 +657,56 @@ android_media_MediaRecorder_native_getMetrics(JNIEnv *env, jobject thiz)
    return mybundle;
    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[] = {
static const JNINativeMethod gMethods[] = {
@@ -689,6 +739,10 @@ static const JNINativeMethod gMethods[] = {
    {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
    {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },


    {"native_getMetrics",    "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},
    {"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
// This function only registers the native methods, and is called from