Search code examples
androidaudiovisualizer

Android Visualizer Detect Beat? And Visualizer.setCaptureSize() not Working? Alternatives?


I am using The Visualizer to draw a Visualization of a Sound File that I am Playing. The sound wave is displaying, however I would like to make it less detailed, as it has an effect on my frame-rate. There is very limited documentation for this. So I have attempted to do the following:

mVisualizer.setCaptureSize(2);

To set the capture rate to a very low value. However, it appears that the line is being drawn with the same amount of detail. I have read in the documentation that:

Sets the capture size, i.e. the number of bytes returned by getWaveForm(byte[]) and getFft(byte[]) methods.

Another issue that I have, is that I would like to detect sounds with a high energy level in the sound File that I am playing so I can visually represent them on the screen. For example: The screen flashes along with the baseline. Here is what I have so far:

public static void setupVisualizer() {
    mVisualizer = new Visualizer(mpSong.getAudioSessionId());
    mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
    mVisualizer.setDataCaptureListener(
            new Visualizer.OnDataCaptureListener() {
                public void onWaveFormDataCapture(Visualizer visualizer,
                        byte[] bytes, int samplingRate) {
                    Game.updateVisualizer(bytes);
                }

                public void onFftDataCapture(Visualizer visualizer,
                        byte[] bytes, int samplingRate) {
                }
            }, Visualizer.getMaxCaptureRate() / 2, true, false);
}

Is it possible to detect certain sounds inside this listener? Or what are the alternatives? Sorry for my bad english. Thank you very much for your time friends.


Solution

  • Ok after several Hours of Testing and Researching I have found a solution. It may not be very accurate, but it is the only alternative I could come up with. I made a class called BeatDetector:

    public class BeatDetectorByFrequency {
    private static final String TAG = "TEST";
    
    private Visualizer mVisualizer = null;
    
    private double mRunningSoundAvg[];
    private double mCurrentAvgEnergyOneSec[];
    private int mNumberOfSamplesInOneSec;
    private long mSystemTimeStartSec;
    // FREQS
    private static final int LOW_FREQUENCY = 300;
    private static final int MID_FREQUENCY = 2500;
    private static final int HIGH_FREQUENCY = 10000;
    private OnBeatDetectedListener onBeatDetectedListener = null;
    
    public BeatDetectorByFrequency() {
        init();
    }
    
    private void init() {
        mRunningSoundAvg = new double[3];
        mCurrentAvgEnergyOneSec = new double[3];
        mCurrentAvgEnergyOneSec[0] = -1;
        mCurrentAvgEnergyOneSec[1] = -1;
        mCurrentAvgEnergyOneSec[2] = -1;
    }
    
    public void link(MediaPlayer player) {
        if (player == null) {
            throw new NullPointerException("Cannot link to null MediaPlayer");
        }
        mVisualizer = new Visualizer(player.getAudioSessionId());
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
    
        Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() {
            @Override
            public void onWaveFormDataCapture(Visualizer visualizer,
                    byte[] bytes, int samplingRate) {
                // DO NOTHING
            }
    
            @Override
            public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
                    int samplingRate) {
                updateVisualizerFFT(bytes);
            }
        };
    
        mVisualizer.setDataCaptureListener(captureListener,
                Visualizer.getMaxCaptureRate() / 2, false, true);
        mVisualizer.setEnabled(true);
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mVisualizer.setEnabled(false);
            }
        });
        mSystemTimeStartSec = System.currentTimeMillis();
    }
    
    public void release() {
        if (mVisualizer != null) {
            mVisualizer.setEnabled(false);
            mVisualizer.release();
        }
    }
    
    public void pause() {
        if (mVisualizer != null) {
            mVisualizer.setEnabled(false);
        }
    }
    
    public void resume() {
        if (mVisualizer != null) {
            mVisualizer.setEnabled(true);
        }
    }
    
    public void updateVisualizerFFT(byte[] audioBytes) {
        int energySum = 0;
        energySum += Math.abs(audioBytes[0]);
        int k = 2;
        double captureSize = mVisualizer.getCaptureSize() / 2;
        int sampleRate = mVisualizer.getSamplingRate() / 2000;
        double nextFrequency = ((k / 2) * sampleRate) / (captureSize);
        while (nextFrequency < LOW_FREQUENCY) {
            energySum += Math.sqrt((audioBytes[k] * audioBytes[k])
                    * (audioBytes[k + 1] * audioBytes[k + 1]));
            k += 2;
            nextFrequency = ((k / 2) * sampleRate) / (captureSize);
        }
        double sampleAvgAudioEnergy = (double) energySum
                / (double) ((k * 1.0) / 2.0);
    
        mRunningSoundAvg[0] += sampleAvgAudioEnergy;
        if ((sampleAvgAudioEnergy > mCurrentAvgEnergyOneSec[0])
                && (mCurrentAvgEnergyOneSec[0] > 0)) {
            fireBeatDetectedLowEvent(sampleAvgAudioEnergy);
        }
        energySum = 0;
        while (nextFrequency < MID_FREQUENCY) {
            energySum += Math.sqrt((audioBytes[k] * audioBytes[k])
                    * (audioBytes[k + 1] * audioBytes[k + 1]));
            k += 2;
            nextFrequency = ((k / 2) * sampleRate) / (captureSize);
        }
    
        sampleAvgAudioEnergy = (double) energySum / (double) ((k * 1.0) / 2.0);
        mRunningSoundAvg[1] += sampleAvgAudioEnergy;
        if ((sampleAvgAudioEnergy > mCurrentAvgEnergyOneSec[1])
                && (mCurrentAvgEnergyOneSec[1] > 0)) {
            fireBeatDetectedMidEvent(sampleAvgAudioEnergy);
        }
        energySum = Math.abs(audioBytes[1]);
    
        while ((nextFrequency < HIGH_FREQUENCY) && (k < audioBytes.length)) {
            energySum += Math.sqrt((audioBytes[k] * audioBytes[k])
                    * (audioBytes[k + 1] * audioBytes[k + 1]));
            k += 2;
            nextFrequency = ((k / 2) * sampleRate) / (captureSize);
        }
    
        sampleAvgAudioEnergy = (double) energySum / (double) ((k * 1.0) / 2.0);
        mRunningSoundAvg[2] += sampleAvgAudioEnergy;
        if ((sampleAvgAudioEnergy > mCurrentAvgEnergyOneSec[2])
                && (mCurrentAvgEnergyOneSec[2] > 0)) {
            fireBeatDetectedHighEvent(sampleAvgAudioEnergy);
        }
    
        mNumberOfSamplesInOneSec++;
        if ((System.currentTimeMillis() - mSystemTimeStartSec) > 1000) {
            mCurrentAvgEnergyOneSec[0] = mRunningSoundAvg[0]
                    / mNumberOfSamplesInOneSec;
            mCurrentAvgEnergyOneSec[1] = mRunningSoundAvg[1]
                    / mNumberOfSamplesInOneSec;
            mCurrentAvgEnergyOneSec[2] = mRunningSoundAvg[2]
                    / mNumberOfSamplesInOneSec;
            mNumberOfSamplesInOneSec = 0;
            mRunningSoundAvg[0] = 0.0;
            mRunningSoundAvg[1] = 0.0;
            mRunningSoundAvg[2] = 0.0;
            mSystemTimeStartSec = System.currentTimeMillis();
        }
    }
    
    // USE INTERFACES IN NEXT UPDATE:
    private void fireBeatDetectedLowEvent(double power) {
        // Utility.log("LOW BEAT DETECTED!");
        Game.lowBeat(power);
        if (onBeatDetectedListener != null) {
            onBeatDetectedListener.onBeatDetectedLow();
        }
    }
    
    private void fireBeatDetectedMidEvent(double power) {
        // Utility.log("MEDIUM BEAT DETECTED!");
        Game.mediumBeat(power);
        if (onBeatDetectedListener != null) {
            onBeatDetectedListener.onBeatDetectedMid();
        }
    }
    
    private void fireBeatDetectedHighEvent(double power) {
        // Utility.log("HIGH BEAT DETECTED!");
        Game.highBeat(power);
        if (onBeatDetectedListener != null) {
            onBeatDetectedListener.onBeatDetectedHigh();
        }
    }
    
    public void setOnBeatDetectedListener(OnBeatDetectedListener listener) {
        onBeatDetectedListener = listener;
    }
    
    public interface OnBeatDetectedListener {
        public abstract void onBeatDetectedLow();
    
        public abstract void onBeatDetectedMid();
    
        public abstract void onBeatDetectedHigh();
    }
    }
    

    It takes a MediaPlayer Object as a parameter and then Calculates Three Different Frequencies Based on the EnergySum of the Byte Data. It is possible to Split the frequencies as many times as you like. I was considering Creating an Array of Frequencies, that each Have a Listener. I then used the Following to Draw a Rectangle:

    public static void highBeat(double power) {
        HIGH_FREQUENCY += (power * 1000); // ORIGINAL: * 1000
        if (HIGH_FREQUENCY > GameValues.FREQ_MAX) {
            HIGH_FREQUENCY = GameValues.FREQ_MAX;
        }
        updateHighFreq();
    }
    
    public static void updateHighFreq() {
        prcnt = HIGH_FREQUENCY * 100 / GameValues.FREQ_MAX;
        if (prcnt < 0)
            prcnt = 0;
        HIGH_F_HEIGHT = (int) (GameValues.FREQ_MAX_HEIGHT * (prcnt / 100));
    }
    

    This calculates the Height of the Rectangle by Calculating a percentage based on the Max Power and Maximum Height of a Bar. It is not very accurate, but it is the best thing I could come up with. Again, this can be done for as many frequencies as you like. Here are some Links that Helped me out:

    https://android.googlesource.com/platform/cts/+/master/tests/tests/media/src/android/media/cts/VisualizerTest.java

    https://www.codota.com/android/scenarios/518916b8da0af8330dfa9398/android.media.audiofx.Visualizer?tag=out_2013_05_05_07_19_34

    Hope I can help anyone else with these issues.