views:

29

answers:

3

I've got an application which records audio from the microphone and then performs some post-processing on the audio in realtime, so I must use the AudioRecord class instead of the standard MediaRecorder. My code for recording is as such:

DataOutputStream dataOutputStreamInstance = new DataOutputStream(bufferedStreamInstance);
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);

int bufferSize = AudioRecord.getMinBufferSize((int)sampleRate, channelConfiguration, DEFAULT_AUDIO_ENCODING) * 2;
short[] microphoneBuffer = new short[bufferSize];
float[] processingBuffer = new float[bufferSize];
short[] outputBuffer = new short[bufferSize];

AudioRecord microphoneRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);

microphoneRecorder.startRecording();
while(isRecording) {
  synchronized(mutex) { ... check for pause condition, wait, etc. ... }
  int numSamplesRead = microphoneRecorder.read(microphoneBuffer, 0, bufferSize);

  // Convert 16-bit short data to floating point
  getFloatingPointBufferFromPcmData(microphoneBuffer, processingBuffer, bufferSize);

  doProcessingStuff(processingBuffer, bufferSize);

  if(numSamplesRead == AudioRecord.ERROR_INVALID_OPERATION) {
    throw new IllegalStateException("read() returned AudioRecord.ERROR_INVALID_OPERATION");
  }
  else if(numSamplesRead == AudioRecord.ERROR_BAD_VALUE) {
    throw new IllegalStateException("read() returned AudioRecord.ERROR_BAD_VALUE");
  }

  try {
    // Dump the output to the target file in 16-bit short format
    getShortPcmBufferFromFloatingPointData(processingBuffer, outputBuffer, bufferSize);
    for(int bufferIndex = 0; bufferIndex < numSamplesRead; bufferIndex++) {
      dataOutputStreamInstance.writeShort(outputBuffer[bufferIndex]);
    }
  }
  catch(Exception e) {
    Log.e("MyApp", "Error while writing audio data to file: " + e.getMessage());
    e.getStackTrace();
  }
}

microphoneRecorder.stop();

The above code works fine, and I can actually record audio from the device, and I hear my voice and such. The problem is that after some seconds, a very strange pattern of distortion starts to emerge until the entire signal is drowned out. Here's a screenshot of a recording of silence which I made by putting some tape over the mic and let the app record for a minute or so:

Screenshot of distortion in wave file

The original wave file can be downloaded here.

The problem is definitely not due to my effect processing code, as I have tried commenting it out and get the same results in both cases. I have scoured the web for other code or people who might be experiencing similar problems, but haven't found anything.

+1  A: 

I don't know Android SDK at all, but getFloatingPointBufferFromPcmData and getShortPcmBufferFromFloatingPointData don't look like standard API functions, despite the lovely naming convention. :)

Did you write these yourself? Perhaps they are using shared state and accumulating results across loop iterations? If these are your implementation, please share the code of these so that we may help you identify the actual problem.

There is also the possibility that you're writing out the PCM data in the wrong format (number of bits, endianness) and that your audio editor is interpreting the data according to a different format resulting in incorrectly decoded audio data which appears deceptively as though there is some accumulation effect happening.

If neither of these inquiries leads you to solve your issue, then my next recommendation would be to create a new microphoneBuffer instance for each loop iteration instead of using a single instance across the while loop.

Again, I'm no Android SDK expert, so these are just general pieces of advice resulting from years of experience dealing with virtually all kinds of APIs and implementation details thereof.

Hope that helps diagnose your issue!

James Dunne
Thanks for the suggestions. Neither one of the buffer conversion functions was at fault, nor was the format conversion (see my own answer here). However, I did add in an extra check to see that the number of bytes read from the microphone was the same as expected, and to my surprise, Android often returns fewer bytes than asked for, so I adapted my code accordingly.
Nik Reiman
Shorting a few bytes I don't think should result in this accumulation effect you're seeing. Have you tried moving the microphoneBuffer (re)allocation to within the loop yet?
James Dunne
Actually the above code works just fine, assuming that I pass `numSamplesRead` instead of `bufferSize` to the buffer conversion and processing functions.
Nik Reiman
I posted that before I saw that you resolved your original issue.
James Dunne
A: 

With silence there might be an automatic gain control that is increasing the input gain beyond reason, trying to find "something" for you to record (and of course finding the noise floor)

What happens if you set your PC speakers to play a nice audio frequency sinusoid - does the noise still come up or do you continue to record the sine wave?

Chris Stratton
A: 

Bah, actually the problem was no fault of Android's -- it was caused by the software I was using to convert the raw PCM data to WAV format. There is evidently some error in endian conversion, since the ARM chips are big endian and WAV is little endian. When we tried opening the raw PCM files in Audacity, they looked fine.

Nik Reiman
I actually did suggest something along those lines could be the problem ;)
James Dunne
Yep, that's true. :) However, the bug is in the conversion software, not my Android code. I did upvote your answer, though, because it got me looking in the right direction.
Nik Reiman