AudioRecordpublic class AudioRecord extends Object 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.
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. |
Fields Summary |
---|
public static final int | STATE_UNINITIALIZEDindicates AudioRecord state is not successfully initialized. | public static final int | STATE_INITIALIZEDindicates AudioRecord state is ready to be used | public static final int | RECORDSTATE_STOPPEDindicates AudioRecord recording state is not recording | public static final int | RECORDSTATE_RECORDINGindicates AudioRecord recording state is recording | public static final int | SUCCESSDenotes a successful operation. | public static final int | ERRORDenotes a generic operation failure. | public static final int | ERROR_BAD_VALUEDenotes a failure due to the use of an invalid value. | public static final int | ERROR_INVALID_OPERATIONDenotes a failure due to the improper use of a method. | private static final int | AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT | private static final int | AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK | private static final int | AUDIORECORD_ERROR_SETUP_INVALIDFORMAT | private static final int | AUDIORECORD_ERROR_SETUP_INVALIDSOURCE | private static final int | AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED | private static final int | NATIVE_EVENT_MARKEREvent id denotes when record head has reached a previously set marker. | private static final int | NATIVE_EVENT_NEW_POSEvent id denotes when previously set update period has elapsed during recording. | private static final String | TAG | public static final String | SUBMIX_FIXED_VOLUME | private long | mNativeRecorderInJavaObjAccessed by native methods: provides access to C++ AudioRecord object | private long | mNativeCallbackCookieAccessed by native methods: provides access to the callback data. | private int | mSampleRateThe audio data sampling rate in Hz. | private int | mChannelCountThe number of input audio channels (1 is mono, 2 is stereo) | private int | mChannelMaskThe audio channel mask | private int | mAudioFormatThe encoding of the audio samples. | private int | mRecordSourceWhere the audio data is recorded from. | private int | mStateIndicates the state of the AudioRecord instance. | private int | mRecordingStateIndicates the recording state of the AudioRecord instance. | private final Object | mRecordingStateLockLock to make sure mRecordingState updates are reflecting the actual state of the object. | private OnRecordPositionUpdateListener | mPositionListenerThe listener the AudioRecord notifies when the record position reaches a marker
or for periodic updates during the progression of the record head. | private final Object | mPositionListenerLockLock to protect position listener updates against event notifications | private NativeEventHandler | mEventHandlerHandler for marker events coming from the native code | private android.os.Looper | mInitializationLooperLooper associated with the thread that creates the AudioRecord instance | private int | mNativeBufferSizeInBytesSize of the native audio buffer. | private int | mSessionIdAudio session ID | private AudioAttributes | mAudioAttributesAudioAttributes | private boolean | mIsSubmixFullVolume | private final android.os.IBinder | mICallBack |
Constructors Summary |
---|
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)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.
//---------------------------------------------------------
// Constructor, Finalize
//--------------------
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);
| public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId)
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 = 0;
if ((format.getPropertySetMask()
& AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
{
rate = format.getSampleRate();
} else {
rate = AudioSystem.getPrimaryOutputSamplingRate();
if (rate <= 0) {
rate = 44100;
}
}
int encoding = AudioFormat.ENCODING_DEFAULT;
if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0)
{
encoding = format.getEncoding();
}
audioParamCheck(attributes.getCapturePreset(), rate, encoding);
mChannelCount = AudioFormat.channelCountFromInChannelMask(format.getChannelMask());
mChannelMask = getChannelMaskFromLegacyConfig(format.getChannelMask(), false);
audioBuffSizeCheck(bufferSizeInBytes);
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.
int initResult = native_setup( new WeakReference<AudioRecord>(this),
mAudioAttributes, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes,
session);
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing native AudioRecord object.");
return; // with mState == STATE_UNINITIALIZED
}
mSessionId = session[0];
mState = STATE_INITIALIZED;
|
Methods Summary |
---|
private void | audioBuffSizeCheck(int audioBufferSize)
// 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.");
}
mNativeBufferSizeInBytes = audioBufferSize;
| private void | audioParamCheck(int audioSource, int sampleRateInHz, int audioFormat)
//--------------
// audio source
if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) ||
((audioSource > MediaRecorder.getAudioSourceMax()) &&
(audioSource != MediaRecorder.AudioSource.FM_TUNER) &&
(audioSource != MediaRecorder.AudioSource.HOTWORD)) ) {
throw new IllegalArgumentException("Invalid audio source.");
}
mRecordSource = audioSource;
//--------------
// sample rate
if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
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_16BIT:
case AudioFormat.ENCODING_PCM_8BIT:
mAudioFormat = audioFormat;
break;
default:
throw new IllegalArgumentException("Unsupported sample encoding."
+ " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT.");
}
| protected void | finalize()
// will cause stop() to be called, and if appropriate, will handle fixed volume recording
release();
| public int | getAudioFormat()Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT}
and {@link AudioFormat#ENCODING_PCM_8BIT}.
return mAudioFormat;
| public int | getAudioSessionId()Returns the audio session ID.
return mSessionId;
| public int | getAudioSource()Returns the audio recording source.
return mRecordSource;
| public int | getChannelConfiguration()Returns the configured channel configuration.
See {@link AudioFormat#CHANNEL_IN_MONO}
and {@link AudioFormat#CHANNEL_IN_STEREO}.
return mChannelMask;
| public int | getChannelCount()Returns the configured number of channels.
return mChannelCount;
| 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;
| public static int | getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)Returns the minimum buffer size required for the successful creation of an AudioRecord
object, in byte units.
Note that this size doesn't guarantee a smooth recording under load, and higher values
should be chosen according to the expected frequency at which the AudioRecord instance
will be polled for new data.
See {@link #AudioRecord(int, int, int, int, int)} for more information on valid
configuration values.
int channelCount = 0;
switch (channelConfig) {
case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
case AudioFormat.CHANNEL_IN_MONO:
case AudioFormat.CHANNEL_CONFIGURATION_MONO:
channelCount = 1;
break;
case AudioFormat.CHANNEL_IN_STEREO:
case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
channelCount = 2;
break;
case AudioFormat.CHANNEL_INVALID:
default:
loge("getMinBufferSize(): Invalid channel configuration.");
return ERROR_BAD_VALUE;
}
// PCM_8BIT is not supported at the moment
if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) {
loge("getMinBufferSize(): Invalid audio format.");
return ERROR_BAD_VALUE;
}
int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
if (size == 0) {
return ERROR_BAD_VALUE;
}
else if (size == -1) {
return ERROR;
}
else {
return size;
}
| public int | getNotificationMarkerPosition()Returns the notification marker position expressed in frames.
return native_get_marker_pos();
| public int | getPositionNotificationPeriod()Returns the notification update period expressed in frames.
return native_get_pos_update_period();
| public int | getRecordingState()Returns the recording state of the AudioRecord instance.
synchronized (mRecordingStateLock) {
return mRecordingState;
}
| public int | getSampleRate()Returns the configured audio data sample rate in Hz
return mSampleRate;
| public int | getState()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.
return mState;
| private void | handleFullVolumeRec(boolean starting)
if (!mIsSubmixFullVolume) {
return;
}
final IBinder b = ServiceManager.getService(android.content.Context.AUDIO_SERVICE);
final IAudioService ias = IAudioService.Stub.asInterface(b);
try {
ias.forceRemoteSubmixFullVolume(starting, mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to AudioService when handling full submix volume", e);
}
| private static void | logd(java.lang.String msg)
Log.d(TAG, msg);
| private static void | loge(java.lang.String msg)
Log.e(TAG, msg);
| private final native void | native_finalize()
| private final native int | native_get_marker_pos()
| private static final native int | native_get_min_buff_size(int sampleRateInHz, int channelCount, int audioFormat)
| private final native int | native_get_pos_update_period()
| private final native int | native_read_in_byte_array(byte[] audioData, int offsetInBytes, int sizeInBytes)
| private final native int | native_read_in_direct_buffer(java.lang.Object jBuffer, int sizeInBytes)
| private final native int | native_read_in_short_array(short[] audioData, int offsetInShorts, int sizeInShorts)
| private final native void | native_release()
| private final native int | native_set_marker_pos(int marker)
| private final native int | native_set_pos_update_period(int updatePeriod)
| private final native int | native_setup(java.lang.Object audiorecord_this, java.lang.Object attributes, int sampleRate, int channelMask, int audioFormat, int buffSizeInBytes, int[] sessionId)
| private final native int | native_start(int syncEvent, int sessionId)
| private final native void | native_stop()
| private static void | postEventFromNative(java.lang.Object audiorecord_ref, int what, int arg1, int arg2, java.lang.Object obj)
//logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
AudioRecord recorder = (AudioRecord)((WeakReference)audiorecord_ref).get();
if (recorder == null) {
return;
}
if (recorder.mEventHandler != null) {
Message m =
recorder.mEventHandler.obtainMessage(what, arg1, arg2, obj);
recorder.mEventHandler.sendMessage(m);
}
| public int | read(byte[] audioData, int offsetInBytes, int sizeInBytes)Reads audio data from the audio hardware for recording into a buffer.
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0)
|| (offsetInBytes + sizeInBytes < 0) // detect integer overflow
|| (offsetInBytes + sizeInBytes > audioData.length)) {
return ERROR_BAD_VALUE;
}
return native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes);
| public int | read(short[] audioData, int offsetInShorts, int sizeInShorts)Reads audio data from the audio hardware for recording into a buffer.
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0)
|| (offsetInShorts + sizeInShorts < 0) // detect integer overflow
|| (offsetInShorts + sizeInShorts > audioData.length)) {
return ERROR_BAD_VALUE;
}
return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts);
| public int | read(java.nio.ByteBuffer audioBuffer, int sizeInBytes)Reads audio data from the audio hardware for recording into a direct buffer. If this buffer
is not a direct buffer, this method will always return 0.
Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
unchanged after a call to this method.
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
if ( (audioBuffer == null) || (sizeInBytes < 0) ) {
return ERROR_BAD_VALUE;
}
return native_read_in_direct_buffer(audioBuffer, sizeInBytes);
| public void | release()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()
try {
stop();
} catch(IllegalStateException ise) {
// don't raise an exception, we're releasing the resources.
}
native_release();
mState = STATE_UNINITIALIZED;
| public int | setNotificationMarkerPosition(int markerInFrames)Sets the marker position at which the listener is called, if set with
{@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener)} or
{@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler)}.
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
return native_set_marker_pos(markerInFrames);
| public int | setPositionNotificationPeriod(int periodInFrames)Sets the period at which the listener is called, if set with
{@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener)} or
{@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler)}.
It is possible for notifications to be lost if the period is too small.
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
return native_set_pos_update_period(periodInFrames);
| public void | setRecordPositionUpdateListener(android.media.AudioRecord$OnRecordPositionUpdateListener listener)Sets the listener the AudioRecord notifies when a previously set marker is reached or
for each periodic record head position update.
setRecordPositionUpdateListener(listener, null);
| public void | setRecordPositionUpdateListener(android.media.AudioRecord$OnRecordPositionUpdateListener listener, android.os.Handler handler)Sets the listener the AudioRecord notifies when a previously set marker is reached or
for each periodic record head position update.
Use this method to receive AudioRecord events in the Handler associated with another
thread than the one in which you created the AudioTrack instance.
synchronized (mPositionListenerLock) {
mPositionListener = listener;
if (listener != null) {
if (handler != null) {
mEventHandler = new NativeEventHandler(this, handler.getLooper());
} else {
// no given handler, use the looper the AudioRecord was created in
mEventHandler = new NativeEventHandler(this, mInitializationLooper);
}
} else {
mEventHandler = null;
}
}
| public void | startRecording()Starts recording from the AudioRecord instance.
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("startRecording() called on an "
+ "uninitialized AudioRecord.");
}
// start recording
synchronized(mRecordingStateLock) {
if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) {
handleFullVolumeRec(true);
mRecordingState = RECORDSTATE_RECORDING;
}
}
| public void | startRecording(MediaSyncEvent syncEvent)Starts recording from the AudioRecord instance when the specified synchronization event
occurs on the specified audio session.
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("startRecording() called on an "
+ "uninitialized AudioRecord.");
}
// start recording
synchronized(mRecordingStateLock) {
if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
handleFullVolumeRec(true);
mRecordingState = RECORDSTATE_RECORDING;
}
}
| public void | stop()Stops recording.
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("stop() called on an uninitialized AudioRecord.");
}
// stop recording
synchronized(mRecordingStateLock) {
handleFullVolumeRec(false);
native_stop();
mRecordingState = RECORDSTATE_STOPPED;
}
|
|