Commit 3c0c2766 authored by Khalid Zubair's avatar Khalid Zubair

Handle recording without using MediaRecorder

MediaRecorder creates a new AudioRecord for the tuner internally and
causes two audio input devices to be in-use during recording. Replace
MediaRecorder with a custom recorder that gets fed by the same
AudioRecord instance used for playback when SW rendering is in affect.

This change helps workaround some bugs in the Audio HAL during fm
recording (two tuner input devices) and concurrent mic recording.

Force SW rendering when recording starts so that the recorder works
and attempt to start the audio patch when recording ends.

In onAudioPatchListUpdate there is no need to call
initAudioRecordSink() before calling startRender() because
startRender() will call initAudioRecordSink().

CYNGNOS-2819, KIPPER-687, FEIJ-1372

Change-Id: Iddd9f325892ca4482c3977dcadc627352e6f5bb2
parent a7db4dc6
This diff is collapsed.
......@@ -21,8 +21,8 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.media.AudioFormat;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
......@@ -41,7 +41,7 @@ import java.util.Locale;
* This class provider interface to recording, stop recording, save recording
* file, play recording file
*/
public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {
public class FmRecorder implements AudioRecorder.Callback {
private static final String TAG = "FmRecorder";
// file prefix
public static final String RECORDING_FILE_PREFIX = "FM";
......@@ -82,7 +82,15 @@ public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.
// listener use for notify service the record state or error state
private OnRecorderStateChangedListener mStateListener = null;
// recorder use for record file
private MediaRecorder mRecorder = null;
private AudioRecorder mRecorder = null;
// take this lock before manipulating mRecorder
private final Object mRecorderLock = new Object();
// format of input audio
private AudioFormat mInputFormat = null;
FmRecorder(AudioFormat in) {
mInputFormat = in;
}
/**
* Start recording the voice of FM, also check the pre-conditions, if not
......@@ -146,31 +154,20 @@ public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.
}
// set record parameter and start recording
try {
mRecorder = new MediaRecorder();
mRecorder.setOnErrorListener(this);
mRecorder.setOnInfoListener(this);
mRecorder.setAudioSource(MediaRecorder.AudioSource.RADIO_TUNER);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
final int samplingRate = 44100;
mRecorder.setAudioSamplingRate(samplingRate);
final int bitRate = 128000;
mRecorder.setAudioEncodingBitRate(bitRate);
final int audiochannels = 2;
mRecorder.setAudioChannels(audiochannels);
mRecorder.setOutputFile(mRecordFile.getAbsolutePath());
mRecorder.prepare();
mRecordStartTime = SystemClock.elapsedRealtime();
mRecorder.start();
mIsRecordingFileSaved = false;
synchronized(mRecorderLock) {
if (mRecorder != null) {
mRecorder.stopRecording();
}
mRecorder = new AudioRecorder(mInputFormat, mRecordFile);
mRecorder.setCallback(this);
mRecordStartTime = SystemClock.elapsedRealtime();
mIsRecordingFileSaved = false;
}
} catch (IllegalStateException e) {
Log.e(TAG, "startRecording, IllegalStateException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
} catch (IOException e) {
Log.e(TAG, "startRecording, IOException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
}
setState(STATE_RECORDING);
}
......@@ -297,17 +294,9 @@ public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.
void onRecorderError(int error);
}
/**
* When recorder occur error, release player, notify error message, and
* update FM recorder state to idle
*
* @param mr The current recorder
* @param what The error message type
* @param extra The error message extra
*/
@Override
public void onError(MediaRecorder mr, int what, int extra) {
Log.e(TAG, "onError, what = " + what + ", extra = " + extra);
public void onError(int what) {
Log.e(TAG, "onError, what = " + what);
stopRecorder();
setError(ERROR_RECORDER_INTERNAL);
if (STATE_RECORDING == mInternalState) {
......@@ -315,23 +304,11 @@ public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.
}
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
Log.d(TAG, "onInfo: what=" + what + ", extra=" + extra);
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
onError(mr, what, extra);
}
}
/**
* Reset FM recorder
*/
public void resetRecorder() {
if (mRecorder != null) {
mRecorder.release();
mRecorder = null;
}
stopRecorder();
mRecordFile = null;
mRecordStartTime = 0;
mRecordTime = 0;
......@@ -523,17 +500,18 @@ public class FmRecorder implements MediaRecorder.OnErrorListener, MediaRecorder.
}
private void stopRecorder() {
synchronized (this) {
synchronized (mRecorderLock) {
if (mRecorder != null) {
try {
mRecorder.stop();
} catch (IllegalStateException ex) {
Log.e(TAG, "stopRecorder, IllegalStateException ocurr " + ex);
setError(ERROR_RECORDER_INTERNAL);
} finally {
mRecorder.release();
mRecorder = null;
}
mRecorder.stopRecording();
mRecorder = null;
}
}
}
public void encode(byte bytes[]) {
synchronized (mRecorderLock) {
if (mRecorder != null) {
mRecorder.encode(bytes);
}
}
}
......
......@@ -510,6 +510,10 @@ public class FmService extends Service implements FmRecorder.OnRecorderStateChan
if (isRender()) {
mAudioTrack.write(tmpBuf, 0, tmpBuf.length);
}
if (mFmRecorder != null) {
mFmRecorder.encode(tmpBuf);
}
} else {
// Earphone mode will come here and wait.
mCurrentFrame = 0;
......@@ -1110,12 +1114,17 @@ public class FmService extends Service implements FmRecorder.OnRecorderStateChan
}
if (mFmRecorder == null) {
mFmRecorder = new FmRecorder();
mFmRecorder = new FmRecorder(mAudioRecord.getFormat());
mFmRecorder.registerRecorderStateListener(FmService.this);
}
if (isSdcardReady(sRecordingSdcard)) {
mFmRecorder.startRecording(mContext);
if (mAudioPatch != null) {
Log.d(TAG, "Switching to SW rendering on recording start");
releaseAudioPatch();
startRender();
}
} else {
onRecorderError(FmRecorder.ERROR_SDCARD_NOT_PRESENT);
}
......@@ -1377,7 +1386,6 @@ public class FmService extends Service implements FmRecorder.OnRecorderStateChan
// Need to recreate AudioRecord and AudioTrack for this case.
if (isPatchMixerToDeviceRemoved(patches)) {
Log.d(TAG, "onAudioPatchListUpdate reinit for BT or WFD connected");
initAudioRecordSink();
startRender();
return;
}
......@@ -1669,32 +1677,44 @@ public class FmService extends Service implements FmRecorder.OnRecorderStateChan
}
startAudioTrack();
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
mAudioManager.listAudioPatches(patches);
if (mAudioPatch == null) {
if (isPatchMixerToEarphone(patches)) {
int status;
stopAudioTrack();
stopRender();
status = createAudioPatch();
if (status != AudioManager.SUCCESS){
Log.d(TAG, "enableFmAudio: fallback as createAudioPatch failed");
startRender();
}
} else {
startRender();
}
}
startPatchOrRender();
} else {
releaseAudioPatch();
stopRender();
}
}
private void startPatchOrRender() {
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
mAudioManager.listAudioPatches(patches);
if (mAudioPatch == null) {
if (isPatchMixerToEarphone(patches)) {
int status;
stopAudioTrack();
stopRender();
status = createAudioPatch();
if (status != AudioManager.SUCCESS){
Log.d(TAG, "startPatchOrRender: fallback as createAudioPatch failed");
startRender();
}
} else {
if (!isRendering()) {
startRender();
}
}
}
}
// Make sure patches count will not be 0
private boolean isPatchMixerToEarphone(ArrayList<AudioPatch> patches) {
int deviceCount = 0;
int deviceEarphoneCount = 0;
if (getRecorderState() == FmRecorder.STATE_RECORDING) {
// force software rendering when recording
return false;
}
if (mContext.getResources().getBoolean(R.bool.config_useSoftwareRenderingForAudio)) {
Log.w(TAG, "FIXME: forcing isPatchMixerToEarphone to return false. "
+ "Software rendering will be used.");
......@@ -1897,6 +1917,15 @@ public class FmService extends Service implements FmRecorder.OnRecorderStateChan
bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.LISTEN_RECORDSTATE_CHANGED);
bundle.putInt(FmListener.KEY_RECORDING_STATE, state);
notifyActivityStateChanged(bundle);
if (state == FmRecorder.STATE_IDLE) { // stopped recording?
if (mPowerStatus == POWER_UP) { // playing?
if (mAudioPatch == null) {
// maybe switch to patch if possible
startPatchOrRender();
}
}
}
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment