/media/java/android/media/AudioRecord.java
http://github.com/android/platform_frameworks_base · Java · 2423 lines · 1096 code · 219 blank · 1108 comment · 255 complexity · c79902af1cb590848d566a9fe92bbe3f MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * Copyright (C) 2008 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.CallbackExecutor;
- import android.annotation.FloatRange;
- import android.annotation.IntDef;
- import android.annotation.IntRange;
- import android.annotation.NonNull;
- import android.annotation.Nullable;
- import android.annotation.RequiresPermission;
- import android.annotation.SystemApi;
- import android.annotation.TestApi;
- import android.app.ActivityThread;
- import android.compat.annotation.UnsupportedAppUsage;
- import android.content.AttributionSource;
- import android.content.AttributionSource.ScopedParcelState;
- import android.content.Context;
- import android.media.MediaRecorder.Source;
- import android.media.audiopolicy.AudioMix;
- import android.media.audiopolicy.AudioPolicy;
- import android.media.metrics.LogSessionId;
- import android.media.projection.MediaProjection;
- import android.os.Binder;
- import android.os.Build;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Looper;
- import android.os.Message;
- import android.os.Parcel;
- import android.os.PersistableBundle;
- import android.os.RemoteException;
- import android.os.ServiceManager;
- import android.util.ArrayMap;
- 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;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.ref.WeakReference;
- import java.nio.ByteBuffer;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Objects;
- import java.util.concurrent.Executor;
- /**
- * The AudioRecord class manages the audio resources for Java applications
- * to record audio from the audio input hardware of the platform. This is
- * achieved by "pulling" (reading) the data from the AudioRecord object. The
- * application is responsible for polling the AudioRecord object in time using one of
- * the following three methods: {@link #read(byte[],int, int)}, {@link #read(short[], int, int)}
- * or {@link #read(ByteBuffer, int)}. The choice of which method to use will be based
- * on the audio data storage format that is the most convenient for the user of AudioRecord.
- * <p>Upon creation, an AudioRecord object initializes its associated audio buffer that it will
- * fill with the new audio data. The size of this buffer, specified during the construction,
- * determines how long an AudioRecord can record before "over-running" data that has not
- * been read yet. Data should be read from the audio hardware in chunks of sizes inferior to
- * the total recording buffer size.</p>
- * <p>
- * Applications creating an AudioRecord instance need
- * {@link android.Manifest.permission#RECORD_AUDIO} or the Builder will throw
- * {@link java.lang.UnsupportedOperationException} on
- * {@link android.media.AudioRecord.Builder#build build()},
- * and the constructor will return an instance in state
- * {@link #STATE_UNINITIALIZED}.</p>
- */
- public class AudioRecord implements AudioRouting, MicrophoneDirection,
- AudioRecordingMonitor, AudioRecordingMonitorClient
- {
- //---------------------------------------------------------
- // Constants
- //--------------------
- /**
- * indicates AudioRecord state is not successfully initialized.
- */
- public static final int STATE_UNINITIALIZED = 0;
- /**
- * indicates AudioRecord state is ready to be used
- */
- public static final int STATE_INITIALIZED = 1;
- /**
- * indicates AudioRecord recording state is not recording
- */
- public static final int RECORDSTATE_STOPPED = 1; // matches SL_RECORDSTATE_STOPPED
- /**
- * indicates AudioRecord recording state is recording
- */
- public static final int RECORDSTATE_RECORDING = 3;// matches SL_RECORDSTATE_RECORDING
- /**
- * Denotes a successful operation.
- */
- public static final int SUCCESS = AudioSystem.SUCCESS;
- /**
- * Denotes a generic operation failure.
- */
- public static final int ERROR = AudioSystem.ERROR;
- /**
- * Denotes a failure due to the use of an invalid value.
- */
- public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE;
- /**
- * Denotes a failure due to the improper use of a method.
- */
- public static final int ERROR_INVALID_OPERATION = AudioSystem.INVALID_OPERATION;
- /**
- * An error code indicating that the object reporting it is no longer valid and needs to
- * be recreated.
- */
- public static final int ERROR_DEAD_OBJECT = AudioSystem.DEAD_OBJECT;
- // Error codes:
- // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp
- private static final int AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT = -16;
- private static final int AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK = -17;
- private static final int AUDIORECORD_ERROR_SETUP_INVALIDFORMAT = -18;
- private static final int AUDIORECORD_ERROR_SETUP_INVALIDSOURCE = -19;
- private static final int AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED = -20;
- // Events:
- // to keep in sync with frameworks/av/include/media/AudioRecord.h
- /**
- * Event id denotes when record head has reached a previously set marker.
- */
- private static final int NATIVE_EVENT_MARKER = 2;
- /**
- * Event id denotes when previously set update period has elapsed during recording.
- */
- private static final int NATIVE_EVENT_NEW_POS = 3;
- private final static String TAG = "android.media.AudioRecord";
- /** @hide */
- public final static String SUBMIX_FIXED_VOLUME = "fixedVolume";
- /** @hide */
- @IntDef({
- READ_BLOCKING,
- READ_NON_BLOCKING
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ReadMode {}
- /**
- * The read mode indicating the read operation will block until all data
- * requested has been read.
- */
- public final static int READ_BLOCKING = 0;
- /**
- * The read mode indicating the read operation will return immediately after
- * reading as much audio data as possible without blocking.
- */
- public final static int READ_NON_BLOCKING = 1;
- //---------------------------------------------------------
- // Used exclusively by native code
- //--------------------
- /**
- * Accessed by native methods: provides access to C++ AudioRecord object
- * Is 0 after release()
- */
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private long mNativeRecorderInJavaObj;
- /**
- * Accessed by native methods: provides access to the callback data.
- */
- @SuppressWarnings("unused")
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private long mNativeCallbackCookie;
- /**
- * Accessed by native methods: provides access to the JNIDeviceCallback instance.
- */
- @SuppressWarnings("unused")
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private long mNativeDeviceCallback;
- //---------------------------------------------------------
- // Member variables
- //--------------------
- private AudioPolicy mAudioCapturePolicy;
- /**
- * The audio data sampling rate in Hz.
- * Never {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED}.
- */
- private int mSampleRate; // initialized by all constructors via audioParamCheck()
- /**
- * The number of input audio channels (1 is mono, 2 is stereo)
- */
- private int mChannelCount;
- /**
- * The audio channel position mask
- */
- private int mChannelMask;
- /**
- * The audio channel index mask
- */
- private int mChannelIndexMask;
- /**
- * The encoding of the audio samples.
- * @see AudioFormat#ENCODING_PCM_8BIT
- * @see AudioFormat#ENCODING_PCM_16BIT
- * @see AudioFormat#ENCODING_PCM_FLOAT
- */
- private int mAudioFormat;
- /**
- * Where the audio data is recorded from.
- */
- private int mRecordSource;
- /**
- * Indicates the state of the AudioRecord instance.
- */
- private int mState = STATE_UNINITIALIZED;
- /**
- * Indicates the recording state of the AudioRecord instance.
- */
- private int mRecordingState = RECORDSTATE_STOPPED;
- /**
- * Lock to make sure mRecordingState updates are reflecting the actual state of the object.
- */
- private final Object mRecordingStateLock = new Object();
- /**
- * The listener the AudioRecord notifies when the record position reaches a marker
- * or for periodic updates during the progression of the record head.
- * @see #setRecordPositionUpdateListener(OnRecordPositionUpdateListener)
- * @see #setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler)
- */
- private OnRecordPositionUpdateListener mPositionListener = null;
- /**
- * Lock to protect position listener updates against event notifications
- */
- private final Object mPositionListenerLock = new Object();
- /**
- * Handler for marker events coming from the native code
- */
- private NativeEventHandler mEventHandler = null;
- /**
- * Looper associated with the thread that creates the AudioRecord instance
- */
- @UnsupportedAppUsage
- private Looper mInitializationLooper = null;
- /**
- * Size of the native audio buffer.
- */
- private int mNativeBufferSizeInBytes = 0;
- /**
- * Audio session ID
- */
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
- /**
- * AudioAttributes
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private AudioAttributes mAudioAttributes;
- private boolean mIsSubmixFullVolume = false;
- /**
- * The log session id used for metrics.
- * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
- */
- @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
- //---------------------------------------------------------
- // Constructor, Finalize
- //--------------------
- /**
- * Class constructor.
- * Though some invalid parameters will result in an {@link IllegalArgumentException} exception,
- * other errors do not. Thus you should call {@link #getState()} immediately after construction
- * to confirm that the object is usable.
- * @param audioSource the recording source.
- * See {@link MediaRecorder.AudioSource} for the recording source definitions.
- * @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
- * rate that is guaranteed to work on all devices, but other rates such as 22050,
- * 16000, and 11025 may work on some devices.
- * {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} means to use a route-dependent value
- * which is usually the sample rate of the source.
- * {@link #getSampleRate()} can be used to retrieve the actual sample rate chosen.
- * @param channelConfig describes the configuration of the audio channels.
- * See {@link AudioFormat#CHANNEL_IN_MONO} and
- * {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed
- * to work on all devices.
- * @param audioFormat the format in which the audio data is to be returned.
- * See {@link AudioFormat#ENCODING_PCM_8BIT}, {@link AudioFormat#ENCODING_PCM_16BIT},
- * and {@link AudioFormat#ENCODING_PCM_FLOAT}.
- * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
- * to during the recording. New audio data can be read from this buffer in smaller chunks
- * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
- * required buffer size for the successful creation of an AudioRecord instance. Using values
- * smaller than getMinBufferSize() will result in an initialization failure.
- * @throws java.lang.IllegalArgumentException
- */
- @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
- public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
- int bufferSizeInBytes)
- throws IllegalArgumentException {
- this((new AudioAttributes.Builder())
- .setInternalCapturePreset(audioSource)
- .build(),
- (new AudioFormat.Builder())
- .setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,
- true/*allow legacy configurations*/))
- .setEncoding(audioFormat)
- .setSampleRate(sampleRateInHz)
- .build(),
- bufferSizeInBytes,
- AudioManager.AUDIO_SESSION_ID_GENERATE);
- }
- /**
- * @hide
- * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
- * @param attributes a non-null {@link AudioAttributes} instance. Use
- * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio
- * source for this instance.
- * @param format a non-null {@link AudioFormat} instance describing the format of the data
- * that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
- * configuring the audio format parameters such as encoding, channel mask and sample rate.
- * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
- * to during the recording. New audio data can be read from this buffer in smaller chunks
- * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
- * required buffer size for the successful creation of an AudioRecord instance. Using values
- * smaller than getMinBufferSize() will result in an initialization failure.
- * @param sessionId ID of audio session the AudioRecord must be attached to, or
- * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
- * time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
- * construction.
- * @throws IllegalArgumentException
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
- public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
- int sessionId) throws IllegalArgumentException {
- this(attributes, format, bufferSizeInBytes, sessionId,
- ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/);
- }
- /**
- * @hide
- * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
- * @param attributes a non-null {@link AudioAttributes} instance. Use
- * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio
- * source for this instance.
- * @param format a non-null {@link AudioFormat} instance describing the format of the data
- * that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
- * configuring the audio format parameters such as encoding, channel mask and sample rate.
- * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
- * to during the recording. New audio data can be read from this buffer in smaller chunks
- * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
- * required buffer size for the successful creation of an AudioRecord instance. Using values
- * smaller than getMinBufferSize() will result in an initialization failure.
- * @param sessionId ID of audio session the AudioRecord must be attached to, or
- * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
- * time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
- * construction.
- * @param context An optional context on whose behalf the recoding is performed.
- *
- * @throws IllegalArgumentException
- */
- private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
- int sessionId, @Nullable Context context,
- int maxSharedAudioHistoryMs) throws IllegalArgumentException {
- mRecordingState = RECORDSTATE_STOPPED;
- if (attributes == null) {
- throw new IllegalArgumentException("Illegal null AudioAttributes");
- }
- if (format == null) {
- throw new IllegalArgumentException("Illegal null AudioFormat");
- }
- // remember which looper is associated with the AudioRecord instanciation
- if ((mInitializationLooper = Looper.myLooper()) == null) {
- mInitializationLooper = Looper.getMainLooper();
- }
- // is this AudioRecord using REMOTE_SUBMIX at full volume?
- if (attributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
- final AudioAttributes.Builder filteredAttr = new AudioAttributes.Builder();
- final Iterator<String> tagsIter = attributes.getTags().iterator();
- while (tagsIter.hasNext()) {
- final String tag = tagsIter.next();
- if (tag.equalsIgnoreCase(SUBMIX_FIXED_VOLUME)) {
- mIsSubmixFullVolume = true;
- Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
- } else { // SUBMIX_FIXED_VOLUME: is not to be propagated to the native layers
- filteredAttr.addTag(tag);
- }
- }
- filteredAttr.setInternalCapturePreset(attributes.getCapturePreset());
- mAudioAttributes = filteredAttr.build();
- } else {
- mAudioAttributes = attributes;
- }
- int rate = format.getSampleRate();
- if (rate == AudioFormat.SAMPLE_RATE_UNSPECIFIED) {
- rate = 0;
- }
- int encoding = AudioFormat.ENCODING_DEFAULT;
- if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0)
- {
- encoding = format.getEncoding();
- }
- audioParamCheck(attributes.getCapturePreset(), rate, encoding);
- if ((format.getPropertySetMask()
- & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0) {
- mChannelIndexMask = format.getChannelIndexMask();
- mChannelCount = format.getChannelCount();
- }
- if ((format.getPropertySetMask()
- & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) {
- mChannelMask = getChannelMaskFromLegacyConfig(format.getChannelMask(), false);
- mChannelCount = format.getChannelCount();
- } else if (mChannelIndexMask == 0) {
- mChannelMask = getChannelMaskFromLegacyConfig(AudioFormat.CHANNEL_IN_DEFAULT, false);
- mChannelCount = AudioFormat.channelCountFromInChannelMask(mChannelMask);
- }
- audioBuffSizeCheck(bufferSizeInBytes);
- AttributionSource attributionSource = (context != null)
- ? context.getAttributionSource() : AttributionSource.myAttributionSource();
- if (attributionSource.getPackageName() == null) {
- // Command line utility
- attributionSource = attributionSource.withPackageName("uid:" + Binder.getCallingUid());
- }
- int[] sampleRate = new int[] {mSampleRate};
- int[] session = new int[1];
- session[0] = sessionId;
- //TODO: update native initialization when information about hardware init failure
- // due to capture device already open is available.
- try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
- int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes,
- sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
- mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(),
- 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs);
- if (initResult != SUCCESS) {
- loge("Error code " + initResult + " when initializing native AudioRecord object.");
- return; // with mState == STATE_UNINITIALIZED
- }
- }
- mSampleRate = sampleRate[0];
- mSessionId = session[0];
- mState = STATE_INITIALIZED;
- }
- /**
- * A constructor which explicitly connects a Native (C++) AudioRecord. For use by
- * the AudioRecordRoutingProxy subclass.
- * @param nativeRecordInJavaObj A C/C++ pointer to a native AudioRecord
- * (associated with an OpenSL ES recorder). Note: the caller must ensure a correct
- * value here as no error checking is or can be done.
- */
- /*package*/ AudioRecord(long nativeRecordInJavaObj) {
- mNativeRecorderInJavaObj = 0;
- mNativeCallbackCookie = 0;
- mNativeDeviceCallback = 0;
- // other initialization...
- if (nativeRecordInJavaObj != 0) {
- deferred_connect(nativeRecordInJavaObj);
- } else {
- mState = STATE_UNINITIALIZED;
- }
- }
- /**
- * 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
- */
- /* package */ void deferred_connect(long nativeRecordInJavaObj) {
- if (mState != STATE_INITIALIZED) {
- int[] session = {0};
- int[] rates = {0};
- //TODO: update native initialization when information about hardware init failure
- // due to capture device already open is available.
- // Note that for this native_setup, we are providing an already created/initialized
- // *Native* AudioRecord, so the attributes parameters to native_setup() are ignored.
- final int initResult;
- try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
- .asScopedParcelState()) {
- initResult = native_setup(new WeakReference<>(this),
- null /*mAudioAttributes*/,
- rates /*mSampleRates*/,
- 0 /*mChannelMask*/,
- 0 /*mChannelIndexMask*/,
- 0 /*mAudioFormat*/,
- 0 /*mNativeBufferSizeInBytes*/,
- session,
- attributionSourceState.getParcel(),
- nativeRecordInJavaObj,
- 0);
- }
- if (initResult != SUCCESS) {
- loge("Error code "+initResult+" when initializing native AudioRecord object.");
- return; // with mState == STATE_UNINITIALIZED
- }
- mSessionId = session[0];
- mState = STATE_INITIALIZED;
- }
- }
- /** @hide */
- public AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
- }
- /**
- * Builder class for {@link AudioRecord} objects.
- * Use this class to configure and create an <code>AudioRecord</code> instance. By setting the
- * recording source and audio format parameters, you indicate which of
- * those vary from the default behavior on the device.
- * <p> Here is an example where <code>Builder</code> is used to specify all {@link AudioFormat}
- * parameters, to be used by a new <code>AudioRecord</code> instance:
- *
- * <pre class="prettyprint">
- * AudioRecord recorder = new AudioRecord.Builder()
- * .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
- * .setAudioFormat(new AudioFormat.Builder()
- * .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
- * .setSampleRate(32000)
- * .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
- * .build())
- * .setBufferSizeInBytes(2*minBuffSize)
- * .build();
- * </pre>
- * <p>
- * If the audio source is not set with {@link #setAudioSource(int)},
- * {@link MediaRecorder.AudioSource#DEFAULT} is used.
- * <br>If the audio format is not specified or is incomplete, its channel configuration will be
- * {@link AudioFormat#CHANNEL_IN_MONO}, and the encoding will be
- * {@link AudioFormat#ENCODING_PCM_16BIT}.
- * The sample rate will depend on the device actually selected for capture and can be queried
- * with {@link #getSampleRate()} method.
- * <br>If the buffer size is not specified with {@link #setBufferSizeInBytes(int)},
- * 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 Context mContext;
- private int mBufferSizeInBytes;
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
- private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
- private int mMaxSharedAudioHistoryMs = 0;
- private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
- private static final int PRIVACY_SENSITIVE_DISABLED = 0;
- private static final int PRIVACY_SENSITIVE_ENABLED = 1;
- /**
- * Constructs a new Builder with the default values as described above.
- */
- public Builder() {
- }
- /**
- * @param source the audio source.
- * See {@link MediaRecorder.AudioSource} for the supported audio source definitions.
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- public Builder setAudioSource(@Source 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);
- }
- mAttributes = new AudioAttributes.Builder()
- .setInternalCapturePreset(source)
- .build();
- return this;
- }
- /**
- * Sets the context the record belongs to. This context will be used to pull information,
- * such as {@link android.content.AttributionSource}, which will be associated with
- * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
- * @param context a non-null {@link Context} instance
- * @return the same Builder instance.
- */
- public @NonNull Builder setContext(@NonNull Context context) {
- Objects.requireNonNull(context);
- // keep reference, we only copy the data when building
- mContext = context;
- return this;
- }
- /**
- * @hide
- * To be only used by system components. Allows specifying non-public capture presets
- * @param attributes a non-null {@link AudioAttributes} instance that contains the capture
- * preset to be used.
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- @SystemApi
- public Builder setAudioAttributes(@NonNull AudioAttributes attributes)
- throws IllegalArgumentException {
- if (attributes == null) {
- throw new IllegalArgumentException("Illegal null AudioAttributes argument");
- }
- if (attributes.getCapturePreset() == MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID) {
- throw new IllegalArgumentException(
- "No valid capture preset in AudioAttributes argument");
- }
- // keep reference, we only copy the data when building
- mAttributes = attributes;
- return this;
- }
- /**
- * Sets the format of the audio data to be captured.
- * @param format a non-null {@link AudioFormat} instance
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- public Builder setAudioFormat(@NonNull AudioFormat format) throws IllegalArgumentException {
- if (format == null) {
- throw new IllegalArgumentException("Illegal null AudioFormat argument");
- }
- // keep reference, we only copy the data when building
- mFormat = format;
- return this;
- }
- /**
- * Sets the total size (in bytes) of the buffer where audio data is written
- * during the recording. New audio data can be read from this buffer in smaller chunks
- * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
- * required buffer size for the successful creation of an AudioRecord instance.
- * Since bufferSizeInBytes may be internally increased to accommodate the source
- * requirements, use {@link #getBufferSizeInFrames()} to determine the actual buffer size
- * in frames.
- * @param bufferSizeInBytes a value strictly greater than 0
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- public Builder setBufferSizeInBytes(int bufferSizeInBytes) throws IllegalArgumentException {
- if (bufferSizeInBytes <= 0) {
- throw new IllegalArgumentException("Invalid buffer size " + bufferSizeInBytes);
- }
- mBufferSizeInBytes = bufferSizeInBytes;
- 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 @NonNull Builder setAudioPlaybackCaptureConfig(
- @NonNull AudioPlaybackCaptureConfiguration config) {
- Preconditions.checkNotNull(
- config, "Illegal null AudioPlaybackCaptureConfiguration argument");
- Preconditions.checkState(
- mAttributes == null,
- ERROR_MESSAGE_SOURCE_MISMATCH);
- mAudioPlaybackCaptureConfiguration = config;
- return this;
- }
- /**
- * Indicates that this capture request is privacy sensitive and that
- * any concurrent capture is not permitted.
- * <p>
- * The default is not privacy sensitive except when the audio source set with
- * {@link #setAudioSource(int)} is {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION} or
- * {@link MediaRecorder.AudioSource#CAMCORDER}.
- * <p>
- * Always takes precedence over default from audio source when set explicitly.
- * <p>
- * Using this API is only permitted when the audio source is one of:
- * <ul>
- * <li>{@link MediaRecorder.AudioSource#MIC}</li>
- * <li>{@link MediaRecorder.AudioSource#CAMCORDER}</li>
- * <li>{@link MediaRecorder.AudioSource#VOICE_RECOGNITION}</li>
- * <li>{@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}</li>
- * <li>{@link MediaRecorder.AudioSource#UNPROCESSED}</li>
- * <li>{@link MediaRecorder.AudioSource#VOICE_PERFORMANCE}</li>
- * </ul>
- * Invoking {@link #build()} will throw an UnsupportedOperationException if this
- * condition is not met.
- * @param privacySensitive True if capture from this AudioRecord must be marked as privacy
- * sensitive, false otherwise.
- */
- public @NonNull Builder setPrivacySensitive(boolean privacySensitive) {
- mPrivacySensitive =
- privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
- return this;
- }
- /**
- * @hide
- * To be only used by system components.
- * @param sessionId ID of audio session the AudioRecord must be attached to, or
- * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at
- * construction time.
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- @SystemApi
- public Builder setSessionId(int sessionId) throws IllegalArgumentException {
- if (sessionId < 0) {
- throw new IllegalArgumentException("Invalid session ID " + sessionId);
- }
- // Do not override a session ID previously set with setSharedAudioEvent()
- if (mSessionId == AudioManager.AUDIO_SESSION_ID_GENERATE) {
- mSessionId = sessionId;
- } else {
- Log.e(TAG, "setSessionId() called twice or after setSharedAudioEvent()");
- }
- return this;
- }
- private @NonNull AudioRecord buildAudioPlaybackCaptureRecord() {
- AudioMix audioMix = mAudioPlaybackCaptureConfiguration.createAudioMix(mFormat);
- MediaProjection projection = mAudioPlaybackCaptureConfiguration.getMediaProjection();
- AudioPolicy audioPolicy = new AudioPolicy.Builder(/*context=*/ null)
- .setMediaProjection(projection)
- .addMix(audioMix).build();
- int error = AudioManager.registerAudioPolicyStatic(audioPolicy);
- if (error != 0) {
- throw new UnsupportedOperationException("Error: could not register audio policy");
- }
- AudioRecord record = audioPolicy.createAudioRecordSink(audioMix);
- if (record == null) {
- throw new UnsupportedOperationException("Cannot create AudioRecord");
- }
- record.unregisterAudioPolicyOnRelease(audioPolicy);
- return record;
- }
- /**
- * @hide
- * Specifies the maximum duration in the past of the this AudioRecord's capture buffer
- * that can be shared with another app by calling
- * {@link AudioRecord#shareAudioHistory(String, long)}.
- * @param maxSharedAudioHistoryMillis the maximum duration that will be available
- * in milliseconds.
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- *
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
- public @NonNull Builder setMaxSharedAudioHistoryMillis(long maxSharedAudioHistoryMillis)
- throws IllegalArgumentException {
- if (maxSharedAudioHistoryMillis <= 0
- || maxSharedAudioHistoryMillis > MAX_SHARED_AUDIO_HISTORY_MS) {
- throw new IllegalArgumentException("Illegal maxSharedAudioHistoryMillis argument");
- }
- mMaxSharedAudioHistoryMs = (int) maxSharedAudioHistoryMillis;
- return this;
- }
- /**
- * @hide
- * Indicates that this AudioRecord will use the audio history shared by another app's
- * AudioRecord. See {@link AudioRecord#shareAudioHistory(String, long)}.
- * The audio session ID set with {@link AudioRecord.Builder#setSessionId(int)} will be
- * ignored if this method is used.
- * @param event The {@link MediaSyncEvent} provided by the app sharing its audio history
- * with this AudioRecord.
- * @return the same Builder instance.
- * @throws IllegalArgumentException
- */
- @SystemApi
- public @NonNull Builder setSharedAudioEvent(@NonNull MediaSyncEvent event)
- throws IllegalArgumentException {
- Objects.requireNonNull(event);
- if (event.getType() != MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY) {
- throw new IllegalArgumentException(
- "Invalid event type " + event.getType());
- }
- if (event.getAudioSessionId() == AudioSystem.AUDIO_SESSION_ALLOCATE) {
- throw new IllegalArgumentException(
- "Invalid session ID " + event.getAudioSessionId());
- }
- // This prevails over a session ID set with setSessionId()
- mSessionId = event.getAudioSessionId();
- return this;
- }
- /**
- * @return a new {@link AudioRecord} instance successfully initialized with all
- * the parameters set on this <code>Builder</code>.
- * @throws UnsupportedOperationException if the parameters set on the <code>Builder</code>
- * were incompatible, or if they are not supported by the device,
- * or if the device was not available.
- */
- @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
- public AudioRecord build() throws UnsupportedOperationException {
- if (mAudioPlaybackCaptureConfiguration != null) {
- return buildAudioPlaybackCaptureRecord();
- }
- if (mFormat == null) {
- mFormat = new AudioFormat.Builder()
- .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
- .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
- .build();
- } else {
- if (mFormat.getEncoding() == AudioFormat.ENCODING_INVALID) {
- mFormat = new AudioFormat.Builder(mFormat)
- .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
- .build();
- }
- if (mFormat.getChannelMask() == AudioFormat.CHANNEL_INVALID
- && mFormat.getChannelIndexMask() == AudioFormat.CHANNEL_INVALID) {
- mFormat = new AudioFormat.Builder(mFormat)
- .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
- .build();
- }
- }
- if (mAttributes == null) {
- mAttributes = new AudioAttributes.Builder()
- .setInternalCapturePreset(MediaRecorder.AudioSource.DEFAULT)
- .build();
- }
- // If mPrivacySensitive is default, the privacy flag is already set
- // according to audio source in audio attributes.
- if (mPrivacySensitive != PRIVACY_SENSITIVE_DEFAULT) {
- int source = mAttributes.getCapturePreset();
- if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
- || source == MediaRecorder.AudioSource.RADIO_TUNER
- || source == MediaRecorder.AudioSource.VOICE_DOWNLINK
- || source == MediaRecorder.AudioSource.VOICE_UPLINK
- || source == MediaRecorder.AudioSource.VOICE_CALL
- || source == MediaRecorder.AudioSource.ECHO_REFERENCE) {
- throw new UnsupportedOperationException(
- "Cannot request private capture with source: " + source);
- }
- mAttributes = new AudioAttributes.Builder(mAttributes)
- .setInternalCapturePreset(source)
- .setPrivacySensitive(mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED)
- .build();
- }
- try {
- // If the buffer size is not specified,
- // use a single frame for the buffer size and let the
- // native code figure out the minimum buffer size.
- if (mBufferSizeInBytes == 0) {
- mBufferSizeInBytes = mFormat.getChannelCount()
- * mFormat.getBytesPerSample(mFormat.getEncoding());
- }
- final AudioRecord record = new AudioRecord(
- mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext,
- mMaxSharedAudioHistoryMs);
- if (record.getState() == STATE_UNINITIALIZED) {
- // release is not necessary
- throw new UnsupportedOperationException("Cannot create AudioRecord");
- }
- return record;
- } catch (IllegalArgumentException e) {
- throw new UnsupportedOperationException(e.getMessage());
- }
- }
- }
- // Convenience method for the constructor's parameter checks.
- // This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
- // IllegalArgumentException-s are thrown
- private static int getChannelMaskFromLegacyConfig(int inChannelConfig,
- boolean allowLegacyConfig) {
- int mask;
- switch (inChannelConfig) {
- case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
- case AudioFormat.CHANNEL_IN_MONO:
- case AudioFormat.CHANNEL_CONFIGURATION_MONO:
- mask = AudioFormat.CHANNEL_IN_MONO;
- break;
- case AudioFormat.CHANNEL_IN_STEREO:
- case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
- mask = AudioFormat.CHANNEL_IN_STEREO;
- break;
- case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
- mask = inChannelConfig;
- break;
- default:
- throw new IllegalArgumentException("Unsupported channel configuration.");
- }
- if (!allowLegacyConfig && ((inChannelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO)
- || (inChannelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO))) {
- // only happens with the constructor that uses AudioAttributes and AudioFormat
- throw new IllegalArgumentException("Unsupported deprecated configuration.");
- }
- return mask;
- }
- // postconditions:
- // mRecordSource is valid
- // mAudioFormat is valid
- // mSampleRate is valid
- private void audioParamCheck(int audioSource, int sampleRateInHz, int audioFormat)
- throws IllegalArgumentException {
- //--------------
- // audio source
- if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) ||
- ((audioSource > MediaRecorder.getAudioSourceMax()) &&
- (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) &&
- (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) &&
- (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) {
- throw new IllegalArgumentException("Invalid audio source " + audioSource);
- }
- mRecordSource = audioSource;
- //--------------
- // sample rate
- if ((sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN ||
- sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) &&
- sampleRateInHz != AudioFormat.SAMPLE_RATE_UNSPECIFIED) {
- throw new IllegalArgumentException(sampleRateInHz
- + "Hz is not a supported sample rate.");
- }
- mSampleRate = sampleRateInHz;
- //--------------
- // audio format
- switch (audioFormat) {
- case AudioFormat.ENCODING_DEFAULT:
- mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
- break;
- case AudioFormat.ENCODING_PCM_24BIT_PACKED:
- case AudioFormat.ENCODING_PCM_32BIT:
- case AudioFormat.ENCODING_PCM_FLOAT:
- case AudioFormat.ENCODING_PCM_16BIT:
- case AudioFormat.ENCODING_PCM_8BIT:
- mAudioFormat = audioFormat;
- break;
- default:
- throw new IllegalArgumentException("Unsupported sample encoding " + audioFormat
- + ". Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,"
- + " ENCODING_PCM_24BIT_PACKED, ENCODING_PCM_32BIT,"
- + " or ENCODING_PCM_FLOAT.");
- }
- }
- // Convenience method for the contructor's audio buffer size check.
- // preconditions:
- // mChannelCount is valid
- // mAudioFormat is AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT,
- // or AudioFormat.ENCODING_PCM_FLOAT
- // postcondition:
- // mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
- private void audioBuffSizeCheck(int audioBufferSize) throws IllegalArgumentException {
- // NB: this section is only valid with PCM data.
- // To update when supporting compressed formats
- int frameSizeInBytes = mChannelCount
- * (AudioFormat.getBytesPerSample(mAudioFormat));
- if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
- throw new IllegalArgumentException("Invalid audio buffer size " + audioBufferSize
- + " (frame size " + frameSizeInBytes + ")");
- }
- mNativeBufferSizeInBytes = audioBufferSize;
- }
- /**
- * Releases the native AudioRecord resources.
- * The object can no longer be used and the reference should be set to null
- * after a call to release()
- */
- public void release() {
- try {
- stop();
- } catch(IllegalStateException ise) {
- // don't raise an exception, we're releasing the resources.
- }
- if (mAudioCapturePolicy != null) {
- AudioManager.unregisterAudioPolicyAsyncStatic(mAudioCapturePolicy);
- mAudioCapturePolicy = null;
- }
- native_release();
- mState = STATE_UNINITIALIZED;
- }
- @Override
- protected void finalize() {
- // will cause stop() to be called, and if appropriate, will handle fixed volume recording
- release();
- }
- //--------------------------------------------------------------------------
- // Getters
- //--------------------
- /**
- * Returns the configured audio sink sample rate in Hz.
- * The sink sample rate never changes after construction.
- * If the constructor had a specific sample rate, then the sink sample rate is that value.
- * If the constructor had {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED},
- * then the sink sample rate is a route-dependent default value based on the source [sic].
- */
- public int getSampleRate() {
- return mSampleRate;
- }
- /**
- * Returns the audio recording source.
- * @see MediaRecorder.AudioSource
- */
- public int getAudioSource() {
- return mRecordSource;
- }
- /**
- * Returns the configured audio data encoding. See {@link AudioFormat#ENCODING_PCM_8BIT},
- * {@link AudioFormat#ENCODING_PCM_16BIT}, and {@link AudioFormat#ENCODING_PCM_FLOAT}.
- */
- public int getAudioFormat() {
- return mAudioFormat;
- }
- /**
- * Returns the configured channel position mask.
- * <p> See {@link AudioFormat#CHANNEL_IN_MONO}
- * and {@link AudioFormat#CHANNEL_IN_STEREO}.
- * This method may return {@link AudioFormat#CHANNEL_INVALID} if
- * a channel index mask is used.
- * Consider {@link #getFormat()} instead, to obtain an {@link AudioFormat},
- * which contains both the channel position mask and the channel index mask.
- */
- public int getChannelConfiguration() {
- return mChannelMask;
- }
- /**
- * Returns the configured <code>AudioRecord</code> format.
- * @return an {@link AudioFormat} containing the
- * <code>AudioRecord</code> parameters at the time of configuration.
- */
- public @NonNull AudioFormat getFormat() {
- AudioFormat.Builder builder = new AudioFormat.Builder()
- .setSampleRate(mSampleRate)
- .setEncoding(mAudioFormat);
- if (mChannelMask != AudioFormat.CHANNEL_INVALID) {
- builder.setChannelMask(mChannelMask);
- }
- if (mChannelIndexMask != AudioFormat.CHANNEL_INVALID /* 0 */) {
- builder.setChannelIndexMask(mChannelIndexMask);
- }
- return builder.build();
- }
- /**
- * Returns the configured number of channels.
- */
- public int getChannelCount() {
- return mChannelCount;
- }
- /**
- * Returns the state of the AudioRecord instance. This is useful after the
- * AudioRecord instance has been created to check if it was initialized
- * properly. This ensures that the appropriate hardware resources have been
- * acquired.
- * @see AudioRecord#STATE_INITIALIZED
- * @see AudioRecord#STATE_UNINITIALIZED
- */
- public int getState() {
- return mState;
- }
- /**
- * Returns…