blob: 6014e712097c6fca6e02a644849ff7418f72c681 [file] [log] [blame]
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.webrtc.voiceengine;
import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReentrantLock;
import android.content.Context;
import android.media.AudioFormat;
import android.media.audiofx.AcousticEchoCanceler;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.AudioEffect.Descriptor;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.os.Build;
import android.util.Log;
class WebRtcAudioRecord {
private AudioRecord _audioRecord = null;
private Context _context;
private ByteBuffer _recBuffer;
private byte[] _tempBufRec;
private final ReentrantLock _recLock = new ReentrantLock();
private boolean _doRecInit = true;
private boolean _isRecording = false;
private int _bufferedRecSamples = 0;
private AcousticEchoCanceler _aec = null;
private boolean _useBuiltInAEC = false;
private static boolean runningOnJellyBeanOrHigher() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
WebRtcAudioRecord() {
try {
_recBuffer = ByteBuffer.allocateDirect(2 * 480); // Max 10 ms @ 48
// kHz
} catch (Exception e) {
DoLog(e.getMessage());
}
_tempBufRec = new byte[2 * 480];
}
public static boolean BuiltInAECIsAvailable() {
// AcousticEchoCanceler was added in API level 16 (Jelly Bean).
if (!runningOnJellyBeanOrHigher()) {
return false;
}
// TODO(henrika): add black-list based on device name. We could also
// use uuid to exclude devices but that would require a session ID from
// an existing AudioRecord object.
return AcousticEchoCanceler.isAvailable();
}
private int EnableBuiltInAEC(boolean enable) {
DoLog("EnableBuiltInAEC(" + enable + ')');
// AcousticEchoCanceler was added in API level 16 (Jelly Bean).
if (!runningOnJellyBeanOrHigher()) {
return -1;
}
_useBuiltInAEC = enable;
// Set AEC state if AEC has already been created.
if (_aec != null) {
int ret = _aec.setEnabled(enable);
if (ret != AudioEffect.SUCCESS) {
DoLogErr("AcousticEchoCanceler.setEnabled failed");
return -1;
}
DoLog("AcousticEchoCanceler.getEnabled: " + _aec.getEnabled());
}
return 0;
}
@SuppressWarnings("unused")
private int InitRecording(int audioSource, int sampleRate) {
DoLog("InitRecording");
audioSource = AudioSource.VOICE_COMMUNICATION;
// get the minimum buffer size that can be used
int minRecBufSize = AudioRecord.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
// DoLog("min rec buf size is " + minRecBufSize);
// double size to be more safe
int recBufSize = minRecBufSize * 2;
// On average half of the samples have been recorded/buffered and the
// recording interval is 1/100s.
_bufferedRecSamples = sampleRate / 200;
// DoLog("rough rec delay set to " + _bufferedRecSamples);
if (_aec != null) {
_aec.release();
_aec = null;
}
// release the object
if (_audioRecord != null) {
_audioRecord.release();
_audioRecord = null;
}
try {
_audioRecord = new AudioRecord(
audioSource,
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
recBufSize);
} catch (Exception e) {
DoLog(e.getMessage());
return -1;
}
// check that the audioRecord is ready to be used
if (_audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
// DoLog("rec not initialized " + sampleRate);
return -1;
}
// DoLog("rec sample rate set to " + sampleRate);
DoLog("AcousticEchoCanceler.isAvailable: " + BuiltInAECIsAvailable());
if (!BuiltInAECIsAvailable()) {
return _bufferedRecSamples;
}
_aec = AcousticEchoCanceler.create(_audioRecord.getAudioSessionId());
if (_aec == null) {
DoLogErr("AcousticEchoCanceler.create failed");
return -1;
}
int ret = _aec.setEnabled(_useBuiltInAEC);
if (ret != AudioEffect.SUCCESS) {
DoLogErr("AcousticEchoCanceler.setEnabled failed");
return -1;
}
Descriptor descriptor = _aec.getDescriptor();
DoLog("AcousticEchoCanceler " +
"name: " + descriptor.name + ", " +
"implementor: " + descriptor.implementor + ", " +
"uuid: " + descriptor.uuid);
DoLog("AcousticEchoCanceler.getEnabled: " + _aec.getEnabled());
return _bufferedRecSamples;
}
@SuppressWarnings("unused")
private int StartRecording() {
DoLog("StartRecording");
// start recording
try {
_audioRecord.startRecording();
} catch (IllegalStateException e) {
e.printStackTrace();
return -1;
}
_isRecording = true;
return 0;
}
@SuppressWarnings("unused")
private int StopRecording() {
DoLog("StopRecording");
_recLock.lock();
try {
// only stop if we are recording
if (_audioRecord.getRecordingState() ==
AudioRecord.RECORDSTATE_RECORDING) {
// stop recording
try {
_audioRecord.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
return -1;
}
}
// Release the AEC object.
if (_aec != null) {
_aec.release();
_aec = null;
}
// Release the AudioRecord object.
_audioRecord.release();
_audioRecord = null;
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
_doRecInit = true;
_recLock.unlock();
}
_isRecording = false;
return 0;
}
@SuppressWarnings("unused")
private int RecordAudio(int lengthInBytes) {
_recLock.lock();
try {
if (_audioRecord == null) {
return -2; // We have probably closed down while waiting for rec
// lock
}
// Set priority, only do once
if (_doRecInit == true) {
try {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
} catch (Exception e) {
DoLog("Set rec thread priority failed: " + e.getMessage());
}
_doRecInit = false;
}
int readBytes = 0;
_recBuffer.rewind(); // Reset the position to start of buffer
readBytes = _audioRecord.read(_tempBufRec, 0, lengthInBytes);
// DoLog("read " + readBytes + "from SC");
_recBuffer.put(_tempBufRec);
if (readBytes != lengthInBytes) {
// DoLog("Could not read all data from sc (read = " + readBytes
// + ", length = " + lengthInBytes + ")");
return -1;
}
} catch (Exception e) {
DoLogErr("RecordAudio try failed: " + e.getMessage());
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
_recLock.unlock();
}
return _bufferedRecSamples;
}
final String logTag = "WebRtcAudioRecord-Java";
private void DoLog(String msg) {
Log.d(logTag, msg);
}
private void DoLogErr(String msg) {
Log.e(logTag, msg);
}
}