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
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).
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
Related
i'm trying to draw audio amplitudes by time, i'm using to achieve this, the AudioRecord class, which gives me a raw audio array.
new Thread(new Runnable() {
#Override
public void run() {
while (mIsRecording) {
int readSize = mRecorder.read(mBuffer, 0, mBuffer.length);
for (int i = 0; i < readSize; i++) {
long time = mChronometer.getTimeElapsed();
ampArray.add((mBuffer[i]));
timeArray.add(time);
}
}
}
}).start();
}
The parameters i use for AudioRecored are:
public static final int SAMPLE_RATE = 8000;
private void initRecorder() {
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
mBuffer = new short[bufferSize];
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, bufferSize);
}
The result i get is this one:
The result i get -----
What i'm looking for
Am I missing something here?
Thanks in advance.
EDIT: Drawing method:
When the recording is stopped, i send all the values saved in the amplitude array and the one in the time array to the LineGraphSeries of the GraphView Api
series = new LineGraphSeries<DataPoint>(generateData(ampArray, timeArray));
graph.addSeries(series);
generateData method:
double x = 0; int i = 0; short y = 0;
private DataPoint[] generateData(ArrayList<Short> ampArray, ArrayList<Double> timeArray) {
DataPoint[] values = new DataPoint[ampArray.size()];
for (int i=0; i< ampArray.size(); i++) {
x = timeArray.get(i);
y = ampArray.get(i);
DataPoint v = new DataPoint(x, y);
values[i] = v;
}
return values;
}
I'm going to take an educated guess here and suggest that it has something to do with these two lines:
long time = mChronometer.getTimeElapsed();
timeArray.add(time);
It looks to me like you are trying to plot the samples which have occurred in a different time regime and you are processing in batch against the current CPU clock which would explain your results - for example you might process a big block of samples - which you can do much faster than they occurred in the first place - and they might all get a similar time axis value.
The proper approach is to reconstruct the time axis for the samples themselves. Assume the first sample you process is time 0. If your sample rate is 48000 then each sample is 1/48000 of a second. The approach would be something like this:
int sampleNumber = 0;
while (mIsRecording) {
int readSize = mRecorder.read(mBuffer, 0, mBuffer.length);
for (int i = 0; i < readSize; i++) {
ampArray.add((mBuffer[i]));
double time = sampleNumber / SAMPLE_RATE;
timeArray.add(time);
sampleNumber++;
}
}
Note, I changed timeArray from int to double as it is now in seconds rather than milliseconds. If you prefer milliseconds then multiply time by 1000 and cast to a long.
Also, you don't need to create an array for the time axis as you can determine the time of any sample based upon its absolute index in the ampArray.
I'm building an android app that pulses an icon - simple pulse, 2x size at loudest volume and 1x at no volume - based on audio. Worth noting my min api is 15.
The user selects the mode (file)to play and I use AudioTrack to play it back on an infinite loop. Each wav sample ranges from < second to 2 or 3 seconds. Audiotrack lets me set the volume and pitch in real-time based on user input (SoundPool wasn't correctly changing pitch in Kitkat).
As the volume changes within each audiotrack, I'm trying to shrink and grow the icon. So far I've tried visualizer to get the waveform and fft data as the track is playing, but I'm not sure that's correct.
Is there a way to get the (nearest possible) real-time db changes from an audiotrack? The wave form function seems to always be between 108 and 112, so I don't think I'm using it correctly. The easiest pulse.wav example is here
My audiotrack init using a byte[] from pcm data
AudioTrack mAudioTrack = new AudioTrack(AudioAudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, getMinBuffer(sound), AudioTrack.MODE_STATIC);
mAudioTrack.write(mSound, 0, mSound.length);
mAudioTrack.setLoopPoints(0, (int)(mSound.length / 4), -1);
My Visualizer
Visualizer mVisualizer = new Visualizer(mAudioTrack.getAudioSessionId());
mVisualizer.setEnabled(false);
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener {
#Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
double sum = 0;
for (int i = 0; i < bytes.length; i++) {
sum += Math.abs(bytes[i]) * Math.abs(bytes[i]);
}
double volume = (double) Math.sqrt(1.0d * sum / bytes.length);
//THIS IS THE RESIZE FUNCTION//
//resizeHeart((double) volume);
System.out.println("Volume: " + volume); //always prints out between 108 and 112.
}
#Override
public void onFftDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
//not sure what to do here.
}
}, Visualizer.getMaxCaptureRate() / 2, true, true);
mVisualizer.setEnabled(true);
The problem is that you're treating the bytes as samples even though you've specified a 16-bit sample size. Try something like this (note the abs is unnecessary since you're squaring anyway):
for (int i = 0; i < bytes.length/2; i+=2) {
int sample = bytes[i] << 8 || bytes[i+1];
sum += sample * sample;
}
Below is the code for my play() method which simply generates an arbitrary set of frequencies and blends them into one tone.
The problem is that it only plays for a split second - I need is to play it continuously. I would appreciate suggestions on how to constantly generate the sound using the AudioTrack class in Android. I believe it has something to do with the MODE_STREAM constant, but I can't quite work out how.
Here is the link to AudioTrack class documentation:
http://developer.android.com/reference/android/media/AudioTrack.html
EDIT: I forgot to mention one important aspect, it can't loop. Due to the mixing of sometimes up to 50+ frequencies, it will sound choppy because there is no least common denominator for all frequency peaks - or it's too far down the waveform to store as one sound.
/**
* play - begins playing the sound
*/
public void play() {
// Get array of frequencies with their relative strengths
double[][] soundData = getData();
// Track samples array
final double samples[] = new double[1024];
// Calculate the average sum in the array and write it to sample
for (int i = 0; i < samples.length; ++i) {
double valueSum = 0;
for (int j = 0; j < soundData.length; j++) {
valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
}
samples[i] = valueSum / soundData.length;
}
// Obtain a minimum buffer size
int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBuffer > 0) {
// Create an AudioTrack
mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBuffer, AudioTrack.MODE_STREAM);
// Begin playing track
mTrack.play();
// Fill the buffer
if (mBuffer.length < samples.length) {
mBuffer = new short[samples.length];
}
for (int k = 0; k < samples.length; k++) {
mBuffer[k] = (short) (samples[k] * Short.MAX_VALUE);
}
// Write audio data to track for real-time audio sythesis
mTrack.write(mBuffer, 0, samples.length);
}
// Once everything has successfully begun, indicate such.
isPlaying = true;
}
It looks like the code is almost there. It just needs a loop to keep generating the samples, putting them in the buffer, and writing them to the AudioTrack. Right now just one buffer full gets written before it exits which is why it stops so quickly.
void getSamples(double[] samples) {
// Get array of frequencies with their relative strengths
double[][] soundData = getData();
// Calculate the average sum in the array and write it to sample
for (int i = 0; i < samples.length; ++i) {
double valueSum = 0;
for (int j = 0; j < soundData.length; j++) {
valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
}
samples[i] = valueSum / soundData.length;
}
}
public void endPlay() {
done = true;
}
/**
* play - begins playing the sound
*/
public void play() {
// Obtain a minimum buffer size
int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBuffer > 0) {
// Create an AudioTrack
mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBuffer, AudioTrack.MODE_STREAM);
// Begin playing track
mTrack.play();
// Track samples array
final double samples[] = new double[1024];
while (!done) {
// Fill the buffer
if (mBuffer.length < samples.length) {
mBuffer = new short[samples.length];
}
getSamples(samples);
for (int k = 0; k < samples.length; k++) {
mBuffer[k] = (short) (samples[k] * Short.MAX_VALUE);
}
// Write audio data to track for real-time audio sythesis
mTrack.write(mBuffer, 0, samples.length);
// Once everything has successfully begun, indicate such.
isPlaying = true;
}
}
// Once everything is done, indicate such.
isPlaying = false;
}
I am making a class that takes an array of frequencies values (i.e. 440Hz, 880Hz, 1760Hz) and plays how they would sound combined into a single AudioTrack. I am not a sound programmer, so this is difficult for me to write myself, where I believe that it is a relatively easy problem to an experienced sound programmer. Here is some of the code below in the play method:
public void play() {
// Get array of frequencies with their relative strengths
double[][] soundData = getData();
// TODO
// Perform a calculation to fill an array with the mixed sound - then play it in an infinite loop
// Need an AudioTrack that will play calculated loop
// Track sample info
int numOfSamples = DURATION * SAMPLE_RATE;
double sample[] = new double[numOfSamples];
byte sound[] = new byte[2 * numOfSamples];
// fill out the array
for (int i = 0; i < numOfSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (SAMPLE_RATE / 440));
}
int i = 0;
for (double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
sound[i++] = (byte) (val & 0x00ff);
sound[i++] = (byte) ((val & 0xff00) >>> 8);
}
// Obtain a minimum buffer size
int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBuffer > 0) {
// Create an AudioTrack
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, numOfSamples, AudioTrack.MODE_STATIC);
// Write audio data to track
track.write(sound, 0, sound.length);
// Begin playing track
track.play();
}
// Once everything has successfully begun, indicate such.
isPlaying = true;
}
Right now, this code simply plays a concert A (440Hz). It was to test whether this code works. Now, I need to take a bunch a frequencies, perform some kind of calculation, and write the sample data.
Ok, so the answer did turn out to be a simple summation loop. Here it is, just replace this for loop with the original one:
// fill out the array
for (int i = 0; i < numOfSamples; ++i) {
double valueSum = 0;
for (int j = 0; j < soundData.length; j++) {
valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
}
sample[i] = valueSum / soundData.length;
}
Now, what this does is simply take all possible frequencies, add them together into the variable, valueSum, and then divide that by the length of the frequency array, soundData, which is a simple average. This produces a nice sine wave mixture of an arbitrarily long array of frequencies.
I haven't tested performance, but I do have this running in a thread, otherwise it could crash the UI. So, hope this helps - I am marking this as the answer.
If you intend to mix multiple waveforms into one, you might prevent clipping in several ways.
Assuming sample[i] is a float representing the sum of all sounds.
HARD CLIPPING:
if (sample[i]> 1.0f)
{
sample[i]= 1.0f;
}
if (sample[i]< -1.0f)
{
sample[i]= -1.0f;
}
HEADROOM (y= 1.1x - 0.2x^3 for the curve, min and max cap slighty under 1.0f)
if (sample[i] <= -1.25f)
{
sample[i] = -0.987654f;
}
else if (sample[i] >= 1.25f)
{
sample[i] = 0.987654f;
}
else
{
sample[i] = 1.1f * sample[i] - 0.2f * sample[i] * sample[i] * sample[i];
}
For a 3rd polynomial waveshapper (less smooth), replace the last line above with:
sample[i]= 1.1f * sample[i]- 0.2f * sample[i] * sample[i] * sample[i];
I am using android platform, from the following reference question I come to know that using AudioRecord class which returns raw data I can filter range of audio frequency depends upon my need but for that I will need algorithm, can somebody please help me out to find algorithm to filter range b/w 14,400 bph and 16,200 bph.
I tried "JTransform" but i don't know can I achieve this with JTransform or not ? Currently I am using "jfftpack" to display visual effects which works very well but i can't achieve audio filter using this.
Reference here
help appreciated Thanks in advance.
Following is my code as i mentioned above i am using "jfftpack" library to display you may find this library reference in the code please don't get confuse with that
private class RecordAudio extends AsyncTask<Void, double[], Void> {
#Override
protected Void doInBackground(Void... params) {
try {
final AudioRecord audioRecord = findAudioRecord();
if(audioRecord == null){
return null;
}
final short[] buffer = new short[blockSize];
final double[] toTransform = new double[blockSize];
audioRecord.startRecording();
while (started) {
final int bufferReadResult = audioRecord.read(buffer, 0, blockSize);
for (int i = 0; i < blockSize && i < bufferReadResult; i++) {
toTransform[i] = (double) buffer[i] / 32768.0; // signed 16 bit
}
transformer.ft(toTransform);
publishProgress(toTransform);
}
audioRecord.stop();
audioRecord.release();
} catch (Throwable t) {
Log.e("AudioRecord", "Recording Failed");
}
return null;
/**
* #param toTransform
*/
protected void onProgressUpdate(double[]... toTransform) {
canvas.drawColor(Color.BLACK);
for (int i = 0; i < toTransform[0].length; i++) {
int x = i;
int downy = (int) (100 - (toTransform[0][i] * 10));
int upy = 100;
canvas.drawLine(x, downy, x, upy, paint);
}
imageView.invalidate();
}
There are a lot of tiny details in this process that can potentially hang you up here. This code isn't tested and I don't do audio filtering very often so you should be extremely suspicious here. This is the basic process you would take for filtering audio:
Get audio buffer
Possible audio buffer conversion (byte to float)
(optional) Apply windowing function i.e. Hanning
Take the FFT
Filter frequencies
Take inverse FFT
I'm assuming you have some basic knowledge of Android and audio recording so will cover steps 4-6 here.
//it is assumed that a float array audioBuffer exists with even length = to
//the capture size of your audio buffer
//The size of the FFT will be the size of your audioBuffer / 2
int FFT_SIZE = bufferSize / 2;
FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE); //this is a jTransforms type
//Take the FFT
mFFT.realForward(audioBuffer);
//The first 1/2 of audioBuffer now contains bins that represent the frequency
//of your wave, in a way. To get the actual frequency from the bin:
//frequency_of_bin = bin_index * sample_rate / FFT_SIZE
//assuming the length of audioBuffer is even, the real and imaginary parts will be
//stored as follows
//audioBuffer[2*k] = Re[k], 0<=k<n/2
//audioBuffer[2*k+1] = Im[k], 0<k<n/2
//Define the frequencies of interest
float freqMin = 14400;
float freqMax = 16200;
//Loop through the fft bins and filter frequencies
for(int fftBin = 0; fftBin < FFT_SIZE; fftBin++){
//Calculate the frequency of this bin assuming a sampling rate of 44,100 Hz
float frequency = (float)fftBin * 44100F / (float)FFT_SIZE;
//Now filter the audio, I'm assuming you wanted to keep the
//frequencies of interest rather than discard them.
if(frequency < freqMin || frequency > freqMax){
//Calculate the index where the real and imaginary parts are stored
int real = 2 * fftBin;
int imaginary = 2 * fftBin + 1;
//zero out this frequency
audioBuffer[real] = 0;
audioBuffer[imaginary] = 0;
}
}
//Take the inverse FFT to convert signal from frequency to time domain
mFFT.realInverse(audioBuffer, false);