blob: b56085bd3c457a0ddee28e677ce68fbde2c571be [file] [log] [blame]
/*
* Copyright (c) 2011 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.
*/
/*
* Android audio device test app
*/
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.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.util.Log;
class AudioDeviceAndroid {
private AudioTrack _audioTrack = null;
private AudioRecord _audioRecord = null;
private Context _context;
private AudioManager _audioManager;
private ByteBuffer _playBuffer;
private ByteBuffer _recBuffer;
private byte[] _tempBufPlay;
private byte[] _tempBufRec;
private final ReentrantLock _playLock = new ReentrantLock();
private final ReentrantLock _recLock = new ReentrantLock();
private boolean _doPlayInit = true;
private boolean _doRecInit = true;
private boolean _isRecording = false;
private boolean _isPlaying = false;
private int _bufferedRecSamples = 0;
private int _bufferedPlaySamples = 0;
private int _playPosition = 0;
AudioDeviceAndroid() {
try {
_playBuffer = ByteBuffer.allocateDirect(2 * 480); // Max 10 ms @ 48
// kHz
_recBuffer = ByteBuffer.allocateDirect(2 * 480); // Max 10 ms @ 48
// kHz
} catch (Exception e) {
DoLog(e.getMessage());
}
_tempBufPlay = new byte[2 * 480];
_tempBufRec = new byte[2 * 480];
}
@SuppressWarnings("unused")
private int InitRecording(int audioSource, int sampleRate) {
// get the minimum buffer size that can be used
int minRecBufSize =
AudioRecord.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
// DoLog("min rec buf size is " + minRecBufSize);
// double size to be more safe
int recBufSize = minRecBufSize * 2;
_bufferedRecSamples = (5 * sampleRate) / 200;
// DoLog("rough rec delay set to " + _bufferedRecSamples);
// release the object
if (_audioRecord != null) {
_audioRecord.release();
_audioRecord = null;
}
try {
_audioRecord = new AudioRecord(
audioSource,
sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_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);
return _bufferedRecSamples;
}
@SuppressWarnings("unused")
private int StartRecording() {
if (_isPlaying == false) {
SetAudioMode(true);
}
// start recording
try {
_audioRecord.startRecording();
} catch (IllegalStateException e) {
e.printStackTrace();
return -1;
}
_isRecording = true;
return 0;
}
@SuppressWarnings("unused")
private int InitPlayback(int sampleRate) {
// get the minimum buffer size that can be used
int minPlayBufSize =
AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
// DoLog("min play buf size is " + minPlayBufSize);
int playBufSize = minPlayBufSize;
if (playBufSize < 6000) {
playBufSize *= 2;
}
_bufferedPlaySamples = 0;
// DoLog("play buf size is " + playBufSize);
// release the object
if (_audioTrack != null) {
_audioTrack.release();
_audioTrack = null;
}
try {
_audioTrack = new AudioTrack(
AudioManager.STREAM_VOICE_CALL,
sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
playBufSize, AudioTrack.MODE_STREAM);
} catch (Exception e) {
DoLog(e.getMessage());
return -1;
}
// check that the audioRecord is ready to be used
if (_audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
// DoLog("play not initialized " + sampleRate);
return -1;
}
// DoLog("play sample rate set to " + sampleRate);
if (_audioManager == null && _context != null) {
_audioManager = (AudioManager)
_context.getSystemService(Context.AUDIO_SERVICE);
}
// Return max playout volume
if (_audioManager == null) {
// Don't know the max volume but still init is OK for playout,
// so we should not return error.
return 0;
}
return _audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
}
@SuppressWarnings("unused")
private int StartPlayback() {
if (_isRecording == false) {
SetAudioMode(true);
}
// start playout
try {
_audioTrack.play();
} catch (IllegalStateException e) {
e.printStackTrace();
return -1;
}
_isPlaying = true;
return 0;
}
@SuppressWarnings("unused")
private int 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 object
_audioRecord.release();
_audioRecord = null;
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
_doRecInit = true;
_recLock.unlock();
}
if (_isPlaying == false) {
SetAudioMode(false);
}
_isRecording = false;
return 0;
}
@SuppressWarnings("unused")
private int StopPlayback() {
_playLock.lock();
try {
// only stop if we are playing
if (_audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
// stop playout
try {
_audioTrack.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
return -1;
}
// flush the buffers
_audioTrack.flush();
}
// release the object
_audioTrack.release();
_audioTrack = null;
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
_doPlayInit = true;
_playLock.unlock();
}
if (_isRecording == false) {
SetAudioMode(false);
}
_isPlaying = false;
return 0;
}
@SuppressWarnings("unused")
private int PlayAudio(int lengthInBytes) {
int bufferedSamples = 0;
_playLock.lock();
try {
if (_audioTrack == null) {
return -2; // We have probably closed down while waiting for
// play lock
}
// Set priority, only do once
if (_doPlayInit == true) {
try {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
} catch (Exception e) {
DoLog("Set play thread priority failed: " + e.getMessage());
}
_doPlayInit = false;
}
int written = 0;
_playBuffer.get(_tempBufPlay);
written = _audioTrack.write(_tempBufPlay, 0, lengthInBytes);
_playBuffer.rewind(); // Reset the position to start of buffer
// DoLog("Wrote data to sndCard");
// increase by number of written samples
_bufferedPlaySamples += (written >> 1);
// decrease by number of played samples
int pos = _audioTrack.getPlaybackHeadPosition();
if (pos < _playPosition) { // wrap or reset by driver
_playPosition = 0; // reset
}
_bufferedPlaySamples -= (pos - _playPosition);
_playPosition = pos;
if (!_isRecording) {
bufferedSamples = _bufferedPlaySamples;
}
if (written != lengthInBytes) {
// DoLog("Could not write all data to sc (written = " + written
// + ", length = " + lengthInBytes + ")");
return -1;
}
} finally {
// Ensure we always unlock, both for success, exception or error
// return.
_playLock.unlock();
}
return bufferedSamples;
}
@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 (_bufferedPlaySamples);
}
@SuppressWarnings("unused")
private int SetPlayoutSpeaker(boolean loudspeakerOn) {
// create audio manager if needed
if (_audioManager == null && _context != null) {
_audioManager = (AudioManager)
_context.getSystemService(Context.AUDIO_SERVICE);
}
if (_audioManager == null) {
DoLogErr("Could not change audio routing - no audio manager");
return -1;
}
int apiLevel = Integer.parseInt(android.os.Build.VERSION.SDK);
if ((3 == apiLevel) || (4 == apiLevel)) {
// 1.5 and 1.6 devices
if (loudspeakerOn) {
// route audio to back speaker
_audioManager.setMode(AudioManager.MODE_NORMAL);
} else {
// route audio to earpiece
_audioManager.setMode(AudioManager.MODE_IN_CALL);
}
} else {
// 2.x devices
if ((android.os.Build.BRAND.equals("Samsung") ||
android.os.Build.BRAND.equals("samsung")) &&
((5 == apiLevel) || (6 == apiLevel) ||
(7 == apiLevel))) {
// Samsung 2.0, 2.0.1 and 2.1 devices
if (loudspeakerOn) {
// route audio to back speaker
_audioManager.setMode(AudioManager.MODE_IN_CALL);
_audioManager.setSpeakerphoneOn(loudspeakerOn);
} else {
// route audio to earpiece
_audioManager.setSpeakerphoneOn(loudspeakerOn);
_audioManager.setMode(AudioManager.MODE_NORMAL);
}
} else {
// Non-Samsung and Samsung 2.2 and up devices
_audioManager.setSpeakerphoneOn(loudspeakerOn);
}
}
return 0;
}
@SuppressWarnings("unused")
private int SetPlayoutVolume(int level) {
// create audio manager if needed
if (_audioManager == null && _context != null) {
_audioManager = (AudioManager)
_context.getSystemService(Context.AUDIO_SERVICE);
}
int retVal = -1;
if (_audioManager != null) {
_audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
level, 0);
retVal = 0;
}
return retVal;
}
@SuppressWarnings("unused")
private int GetPlayoutVolume() {
// create audio manager if needed
if (_audioManager == null && _context != null) {
_audioManager = (AudioManager)
_context.getSystemService(Context.AUDIO_SERVICE);
}
int level = -1;
if (_audioManager != null) {
level = _audioManager.getStreamVolume(
AudioManager.STREAM_VOICE_CALL);
}
return level;
}
private void SetAudioMode(boolean startCall) {
int apiLevel = Integer.parseInt(android.os.Build.VERSION.SDK);
if (_audioManager == null && _context != null) {
_audioManager = (AudioManager)
_context.getSystemService(Context.AUDIO_SERVICE);
}
if (_audioManager == null) {
DoLogErr("Could not set audio mode - no audio manager");
return;
}
// ***IMPORTANT*** When the API level for honeycomb (H) has been
// decided,
// the condition should be changed to include API level 8 to H-1.
if ((android.os.Build.BRAND.equals("Samsung") || android.os.Build.BRAND
.equals("samsung")) && (8 == apiLevel)) {
// Set Samsung specific VoIP mode for 2.2 devices
int mode =
(startCall ? 4 /* VoIP mode */
: AudioManager.MODE_NORMAL);
_audioManager.setMode(mode);
if (_audioManager.getMode() != mode) {
DoLogErr("Could not set audio mode for Samsung device");
}
}
}
final String logTag = "WebRTC AD java";
private void DoLog(String msg) {
Log.d(logTag, msg);
}
private void DoLogErr(String msg) {
Log.e(logTag, msg);
}
}