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

Commit d32a1d0a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce audio playback capture API"

parents e5445c7c 669be044
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -23371,6 +23371,18 @@ package android.media {
    method public void onAudioFocusChange(int);
  }
  public final class AudioPlaybackCaptureConfiguration {
  }
  public static final class AudioPlaybackCaptureConfiguration.Builder {
    ctor public AudioPlaybackCaptureConfiguration.Builder();
    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
    method public android.media.AudioPlaybackCaptureConfiguration build();
    method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
    method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(@NonNull android.media.AudioAttributes);
  }
  public final class AudioPlaybackConfiguration implements android.os.Parcelable {
    method public int describeContents();
    method public android.media.AudioAttributes getAudioAttributes();
@@ -23469,6 +23481,7 @@ package android.media {
    ctor public AudioRecord.Builder();
    method public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
    method public android.media.AudioRecord.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
    method public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
    method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
    method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
  }
+8 −0
Original line number Diff line number Diff line
@@ -3188,6 +3188,10 @@ public class AudioManager {
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
    public int registerAudioPolicy(@NonNull AudioPolicy policy) {
        return registerAudioPolicyStatic(policy);
    }

    static int registerAudioPolicyStatic(@NonNull AudioPolicy policy) {
        if (policy == null) {
            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
        }
@@ -3214,6 +3218,10 @@ public class AudioManager {
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
    public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
        unregisterAudioPolicyAsyncStatic(policy);
    }

    static void unregisterAudioPolicyAsyncStatic(@NonNull AudioPolicy policy) {
        if (policy == null) {
            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
        }
+161 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.
 */

package android.media;

import android.annotation.NonNull;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;

import com.android.internal.util.Preconditions;

/**
 * Configuration for capturing audio played by other apps.
 *
 * <p>An example for creating a capture configuration for capturing all media playback:
 *
 * <pre>
 *     AudioAttributes mediaAttr = new AudioAttributes.Builder()
 *         .setUsage(AudioAttributes.USAGE_MEDIA)
 *         .build();
 *     AudioPlaybackCaptureConfiguration config = new AudioPlaybackCaptureConfiguration.Builder()
 *         .addMatchingUsage(mediaAttr)
 *         .build();
 *     AudioRecord record = new AudioRecord.Builder()
 *         .setPlaybackCaptureConfig(config)
 *         .build();
 * </pre>
 *
 * @see AudioRecord.Builder#setPlaybackCaptureConfig(AudioPlaybackCaptureConfiguration)
 */
public final class AudioPlaybackCaptureConfiguration {

    private final AudioMixingRule mAudioMixingRule;

    private AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule) {
        mAudioMixingRule = audioMixingRule;
    }

    /**
     * Returns a mix that routes audio back into the app while still playing it from the speakers.
     *
     * @param audioFormat The format in which to capture the audio.
     */
    AudioMix createAudioMix(AudioFormat audioFormat) {
        return new AudioMix.Builder(mAudioMixingRule)
                .setFormat(audioFormat)
                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER)
                .build();
    }

    /** Builder for creating {@link AudioPlaybackCaptureConfiguration} instances. */
    public static final class Builder {

        private static final int MATCH_TYPE_UNSPECIFIED = 0;
        private static final int MATCH_TYPE_INCLUSIVE = 1;
        private static final int MATCH_TYPE_EXCLUSIVE = 2;

        private static final String ERROR_MESSAGE_MISMATCHED_RULES =
                "Inclusive and exclusive usage rules cannot be combined";

        private final AudioMixingRule.Builder mAudioMixingRuleBuilder;
        private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED;
        private int mUidMatchType = MATCH_TYPE_UNSPECIFIED;

        public Builder() {
            mAudioMixingRuleBuilder = new AudioMixingRule.Builder();
        }

        /**
         * Only capture audio output with the given {@link AudioAttributes}.
         *
         * <p>If called multiple times, will capture audio output that matches any of the given
         * attributes.
         *
         * @throws IllegalStateException if called in conjunction with
         *     {@link #excludeUsage(AudioAttributes)}.
         */
        public Builder addMatchingUsage(@NonNull AudioAttributes audioAttributes) {
            Preconditions.checkNotNull(audioAttributes);
            Preconditions.checkState(
                    mUsageMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
            mAudioMixingRuleBuilder
                    .addRule(audioAttributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
            mUsageMatchType = MATCH_TYPE_INCLUSIVE;
            return this;
        }

        /**
         * Only capture audio output by app with the matching {@code uid}.
         *
         * <p>If called multiple times, will capture audio output by apps whose uid is any of the
         * given uids.
         *
         * @throws IllegalStateException if called in conjunction with {@link #excludeUid(int)}.
         */
        public Builder addMatchingUid(int uid) {
            Preconditions.checkState(
                    mUidMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
            mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
            mUidMatchType = MATCH_TYPE_INCLUSIVE;
            return this;
        }

        /**
         * Only capture audio output that does not match the given {@link AudioAttributes}.
         *
         * <p>If called multiple times, will capture audio output that does not match any of the
         * given attributes.
         *
         * @throws IllegalStateException if called in conjunction with
         *     {@link #addMatchingUsage(AudioAttributes)}.
         */
        public Builder excludeUsage(@NonNull AudioAttributes audioAttributes) {
            Preconditions.checkNotNull(audioAttributes);
            Preconditions.checkState(
                    mUsageMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
            mAudioMixingRuleBuilder.excludeRule(audioAttributes,
                    AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
            mUsageMatchType = MATCH_TYPE_EXCLUSIVE;
            return this;
        }

        /**
         * Only capture audio output by apps that do not have the matching {@code uid}.
         *
         * <p>If called multiple times, will capture audio output by apps whose uid is not any of
         * the given uids.
         *
         * @throws IllegalStateException if called in conjunction with {@link #addMatchingUid(int)}.
         */
        public Builder excludeUid(int uid) {
            Preconditions.checkState(
                    mUidMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
            mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
            mUidMatchType = MATCH_TYPE_EXCLUSIVE;
            return this;
        }

        /**
         * Builds the configuration instance.
         *
         * @throws UnsupportedOperationException if the parameters set are incompatible.
         */
        public AudioPlaybackCaptureConfiguration build() {
            return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build());
        }
    }
}
+58 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -37,6 +39,7 @@ import android.util.Log;
import android.util.Pair;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.io.IOException;
import java.lang.annotation.Retention;
@@ -182,6 +185,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
    //---------------------------------------------------------
    // Member variables
    //--------------------
    private AudioPolicy mAudioCapturePolicy;

    /**
     * The audio data sampling rate in Hz.
     * Never {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED}.
@@ -428,6 +433,16 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        }
    }

    /**
     * Sets an {@link AudioPolicy} to automatically unregister when the record is released.
     *
     * <p>This is to prevent users of the audio capture API from having to manually unregister the
     * policy that was used to create the record.
     */
    private void unregisterAudioPolicyOnRelease(AudioPolicy audioPolicy) {
        mAudioCapturePolicy = audioPolicy;
    }

    /**
     * @hide
     */
@@ -491,6 +506,11 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
     * the minimum buffer size for the source is used.
     */
    public static class Builder {

        private static final String ERROR_MESSAGE_SOURCE_MISMATCH =
                "Cannot both set audio source and set playback capture config";

        private AudioPlaybackCaptureConfiguration mAudioPlaybackCaptureConfiguration;
        private AudioAttributes mAttributes;
        private AudioFormat mFormat;
        private int mBufferSizeInBytes;
@@ -509,6 +529,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
         * @throws IllegalArgumentException
         */
        public Builder setAudioSource(int source) throws IllegalArgumentException {
            Preconditions.checkState(
                    mAudioPlaybackCaptureConfiguration == null,
                    ERROR_MESSAGE_SOURCE_MISMATCH);
            if ( (source < MediaRecorder.AudioSource.DEFAULT) ||
                    (source > MediaRecorder.getAudioSourceMax()) ) {
                throw new IllegalArgumentException("Invalid audio source " + source);
@@ -577,6 +600,25 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
            return this;
        }

        /**
         * Sets the {@link AudioRecord} to record audio played by other apps.
         *
         * @param config Defines what apps to record audio from (i.e., via either their uid or
         *               the type of audio).
         * @throws IllegalStateException if called in conjunction with {@link #setAudioSource(int)}.
         * @throws NullPointerException if {@code config} is null.
         */
        public Builder setAudioPlaybackCaptureConfig(
                @NonNull AudioPlaybackCaptureConfiguration config) {
            Preconditions.checkNotNull(
                    config, "Illegal null AudioPlaybackCaptureConfiguration argument");
            Preconditions.checkState(
                    mAttributes == null,
                    ERROR_MESSAGE_SOURCE_MISMATCH);
            mAudioPlaybackCaptureConfiguration = config;
            return this;
        }

        /**
         * @hide
         * To be only used by system components.
@@ -595,6 +637,15 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
            return this;
        }

        private AudioRecord buildAudioPlaybackCaptureRecord() {
            AudioMix audioMix = mAudioPlaybackCaptureConfiguration.createAudioMix(mFormat);
            AudioPolicy audioPolicy = new AudioPolicy.Builder(/*context=*/ null)
                    .addMix(audioMix).build();
            AudioRecord record = audioPolicy.createAudioRecordSink(audioMix);
            record.unregisterAudioPolicyOnRelease(audioPolicy);
            return record;
        }

        /**
         * @return a new {@link AudioRecord} instance successfully initialized with all
         *     the parameters set on this <code>Builder</code>.
@@ -603,6 +654,10 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
         *     or if the device was not available.
         */
        public AudioRecord build() throws UnsupportedOperationException {
            if (mAudioPlaybackCaptureConfiguration != null) {
                return buildAudioPlaybackCaptureRecord();
            }

            if (mFormat == null) {
                mFormat = new AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
@@ -757,6 +812,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        } catch(IllegalStateException ise) {
            // don't raise an exception, we're releasing the resources.
        }
        if (mAudioCapturePolicy != null) {
            AudioManager.unregisterAudioPolicyAsyncStatic(mAudioCapturePolicy);
        }
        native_release();
        mState = STATE_UNINITIALIZED;
    }