4

I have a code to compute real-time dB Amplitude of AudioRecord. The code works well for computing dB Amplitude. After recording, I save that it to wav file. Now, I want to playback that file and recompute the dB Amplitude. However, I cannot achieve similar result before. Could you help me to fix it. This is my code to compute dB Amplitude when recording and playback.

1.Compute dB amplitude when recording

bufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT);
record = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT, bufferSize);
audioBuffer = new short[bufferSize];
readSize=record.read(audioBuffer, 0, audioBuffer.length);
double amplitude = 0;
double sum=0;
for (int i = 0; i < readSize; i++) {
     sum += audioBuffer[i] * audioBuffer[i];
}
amplitude = sum / readSize;
dbAmp=20.0 *Math.log10(amplitude/32767.0);

2.Assume that the file output is ouput.wav. I used MediaPlayer to playback and compute Amplitude

String filePath = Environment.getExternalStorageDirectory().getPath() +"/" +"output.wav";
mPlayer = new  MediaPlayer();
mPlayer.setDataSource(filePath);
mPlayer.prepare();
mPlayer.start();
mVisualizerView.link(mPlayer);

In which, mVisualizerView is Visualizer class. The class has link function such as

 public void link(MediaPlayer player)
  {
    // Create the Visualizer object and attach it to our media player.
    mVisualizer = new Visualizer(player.getAudioSessionId());
    mVisualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);
    mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
    // Pass through Visualizer data to VisualizerView
    Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener()
    {
      @Override
      public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
          int samplingRate)
      {       
        updateVisualizer(bytes);
      }
      @Override
      public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
          int samplingRate)
      {     
        updateVisualizerFFT(bytes);
      }
    };
    mVisualizer.setDataCaptureListener(captureListener,
        Visualizer.getMaxCaptureRate() / 2, true, true);
    player.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
    {
      @Override
      public void onCompletion(MediaPlayer mediaPlayer)
      {
        mVisualizer.setEnabled(false);
      }
    });
  } 

As my task, I will recompute dbAmp from bytes in functions updateVisualizer or updateVisualizerFFT

   public void updateVisualizer(byte[] bytes) {
    dbAmp = computedbAmp(bytes); 
    mBytes = bytes;
    invalidate();
  }
  public void updateVisualizerFFT(byte[] bytes) {
    dbAmp = computedbAmp(bytes);
    mFFTBytes = bytes;
    invalidate();
  }
  public double computedbAmp(byte[] audioData) {
        //System.out.println("::::: audioData :::::"+audioData);
      double amplitude = 0;
      for (int i = 0; i < audioData.length/2; i++) {
          double y = (audioData[i*2] | audioData[i*2+1] << 8) / 32768.0;
          // depending on your endianness:
          // double y = (audioData[i*2]<<8 | audioData[i*2+1]) / 32768.0
          amplitude += Math.abs(y);
      }
      amplitude = amplitude / audioData.length / 2;
      return amplitude;
    }

Currently, I apply some way to compute dB amplitude from bytes. However, they are not correct. Could you help me to fix it or suggest to me the solution to compute it? Thanks

My expected solution such as Sensor Box for Android enter image description here

Jame
  • 3,746
  • 6
  • 52
  • 101
  • To get the same result, it helps to use the same formula. – Henry Sep 28 '15 at 13:33
  • @Henry: You are right. However, in the first case, I used short array. While output of MediaPlayer is byte array. Then these formulas have a little different. Do you have any idea to correct them? – Jame Sep 28 '15 at 13:47
  • 2
    In the first case you average the square of the amplitude and you take a log. None of that happens in the second case. – Henry Sep 28 '15 at 13:52
  • How to correct them? Note that the output of second case is byte array. Thanks – Jame Sep 28 '15 at 15:19

2 Answers2

4

As mentioned in the comments you are not using the same computation for both. Also, I don't think either method is correct.

From your code in the first example it looks like you are trying to compute the RMS which is the sqrt(sumOfSquares/N) and then convert to dB.

The second sample is sumOfAbs/N not converted to dB

Another very minor issue is that in one case you divide by 32767 and the other 32768. Both should be 32768.

For part one do something like this:

double sum=0;
for (int i = 0; i < readSize; i++) {
    double y = audioBuffer[i] / 32768.0;
    sum += y * y;
}
double rms = Math.sqrt(sum / readSize);
dbAmp=20.0 *Math.log10(rms);

And for part 2:

double sum=0;
for (int i = 0; i < audioData.length/2; i++) {
    double y = (audioData[i*2] | audioData[i*2+1] << 8) / 32768.0;
    sum += y * y;
}
double rms = Math.sqrt(sum / audioData.length/2);
dbAmp = 20.0*Math.log10(rms);

Notice the two are almost exactly identical with the exception of cracking open the byte array. This should be a clue to you to find a way to factor out this function and then you won't run into this kind of problem in the future.

Edit:

One more thing I forgot to mention. There is a bit of open debate on this matter but depending on your application you might want your dBFS result to be sine calibrated. What I mean that is you were to run the computation on a single full scale sine wave as I've written it you would get a rms value of 0.7071 (1/sqrt(2)), or -3dBFS. If you want a full scale sine to hit exactly zero dBFS you need to multiply the rms value by sqrt(2).

jaket
  • 9,140
  • 2
  • 25
  • 44
  • Thank jaket for your comment. However, I run it, the result returned negative values for wav file. In additions, the readSize in second case is audioData.length/2, right? – Jame Sep 29 '15 at 01:02
  • Negative values are exactly what you should be expecting since you are computing dB relative to digital full scale. – jaket Sep 29 '15 at 01:16
  • Can you look at my update question? I add a image that get from one application. How they calculate dB for sound? It has positive value which is my expected solution. I used my first case code and give similar result with that application. However, it does not true for second my and you cases. Thanks – Jame Sep 29 '15 at 01:40
  • That's because they are computing dB relative to another reference - probably dBSPL. In order for you to do this you would need to know the sensitivity of your microphone. In other words, by the time you are accessing the samples they have gone through a A/D converter and you've lost any notion of the analog gain at the front end. – jaket Sep 29 '15 at 02:08
  • 1
    And btw this is morphing into a completely different question from your original question about why your two algorithms are different. That part has been answered so I would suggest you do some investigation about what dBSPL means and maybe ask another question. – jaket Sep 29 '15 at 02:09
  • Thank jaket for Sound Pressure Level (SPL) meaning. I think that the first case is compute from Sound Pressure Level as you mention, Right? Is it possible to compute dBSPL from second case. If it is possible. I will make another question. Thanks – Jame Sep 29 '15 at 02:13
  • 2
    No, its not SPL. It's only a digital level. To convert to SPL you *must* have some information about the microphone. For example, if you know your microphone, presented with a +90 dBSPL sound will produce a -10dBFS (that's what my answer is giving you) signal, then you can say you can just take the number out of the code I posted and add 100 to it and say its dBSPL. Figuring out that number is not a software problem though. Sorry. – jaket Sep 29 '15 at 02:25
  • Are you sure that rms in your first and second cases will be give similar results? Because the first case compute for short array, while second case is byte array – Jame Sep 29 '15 at 05:51
  • The second case creates a short on the stack `(audioData[i*2] | audioData[i*2+1] << 8)`. I just copied this from your question. You might need to cast to shorts to get things to work though. `(((short)audioData[i*2]) | ((short)(audioData[i*2+1]) << 8)` – jaket Sep 29 '15 at 05:54
  • I test it but the rms give wrong value. From reference and your comment, I think the first case to compute rms is correct. But the second case still is wrong. This is my reference http://developer.samsung.com/technical-doc/view.do;jsessionid=MyYvWKWJnT89Kw19mSSgT6Vrg5nwQQyvZDDgmTD8vgz6yYJrGP91!721091146?v=T000000086 – Jame Sep 29 '15 at 05:58
  • In what way is it wrong? Give me some values. Try swapping the bytes `(((short)audioData[i*2+1]) | ((short)(audioData[i*2]) << 8)` maybe it's an endian problem. Also keep in mind that the first code computes the level for the entire track and the second does it for shorter chunks. – jaket Sep 29 '15 at 06:14
  • The second way is wrong. The first case gives values such as 0 for no sound and 200 to 300 with sound (without normalization). However, the second case gives 0.49 for no sound and 0.21 with sound. I don't know why rms with no sound is bigger than with sound? Is it possible to compute rms without normalization to get value same first case? – Jame Sep 29 '15 at 06:29
  • 2
    idk. they're exactly the same except where y is assigned. they're also both doing the same normalization so I'm not sure what you're getting at by that question. try stepping through in a debugger with some hard coded values like 32767 and -32768. – jaket Sep 29 '15 at 06:38
  • Could we discuss in chat room at https://chat.stackoverflow.com/rooms/90858/compute-decibel-db-of-amplitude – Jame Sep 29 '15 at 06:42
1

As question said that first case worked well. Hence, I assumed first case was correct and used it as reference to edit his second case. From comment of jaket, we can modify the second case as

  double sum=0;
  for (int i = 0; i < audioData.length/2; i++) {
      double y = (audioData[i*2] | audioData[i*2+1] << 8);
      sum += y*y;
  }
  double rms = sum / audioData.length/2;
  double dbAmp = 20.0*Math.log10(rms/32768.0);
  return dbAmp;

I think it will be same result with first case. Hope it help

John
  • 2,838
  • 7
  • 36
  • 65
  • while it will produce the same result it will produce the wrong result. Notably the OP neglected the sqrt and the scaling needs to be applied before the squaring. – jaket Sep 29 '15 at 02:11
  • Yes, I think so. I think he want to compute dBSPL as your comment. But I have no expert in the dBSPL. – John Sep 29 '15 at 02:16
  • @jaket yes, the formula is wrong. The missing square root can be compensated by multiplying the log with 10 instead of 20. The wrong scaling just causes a shift of the 0dB level and can probably be neglected in this application. – Henry Sep 29 '15 at 04:10
  • @henry Sure you could do it in that unconventional way. My point was directed toward the fact that this answer copied directly from mine where I renamed the variable named amplitude to rms. Yet the lack of the square root changes completely changes the meaning. And it is being multiplied by 20 not 10 so as I said, it gives the wrong result. – jaket Sep 29 '15 at 04:23
  • @jaket: What does it means of normalization amplitude range [0,1]. Because I often see the amplitude of sound is real number without normalization which bigger than 1, such as 120,100...Is it common in sound level ploting? My reference formula in http://developer.samsung.com/technical-doc/view.do;jsessionid=MyYvWKWJnT89Kw19mSSgT6Vrg5nwQQyvZDDgmTD8vgz6yYJrGP91!721091146?v=T000000086 – Jame Sep 29 '15 at 04:44
  • 2
    fwi you are commenting on another user's answer from mine. 16-bit samples range between -32768 and 32767. When you divide by 32768 you get values between -1 and 1. It's common to normalize the samples straight away so that any logic downstream becomes independent of the original bit depth. And it's just easier to think of it that way. – jaket Sep 29 '15 at 05:43