来自Android FFT输出(可视化器)的恶劣频谱?

5

我有一个关于FFT的问题(实际上我相信这更多是关于Android Visualizer.getFFT()的输出)。

我创建了一个音乐播放器,包括很多功能(如流派、动态播放列表和可视化),目前在创建可视化时遇到了一些麻烦,尤其是在渲染当前音频流的频谱时。

我已经阅读了以下问题(和答案),以便了解Android FFT:

应该从getFft得到什么样的输出?

Android 2.3 Visualizer - 难以理解getFft()

现在是我的问题:我从getFFTs系数中得到的频谱似乎有些“奇怪”。我注意到我呈现的频谱似乎在播放歌曲时显示了很多“噪声”,因此我尝试使用一些测试声音。其中之一是一个简单的8khz声音,应该在图形中只产生一个峰值。不幸的是,结果看起来像下面这样:

http://img4.imageshack.us/img4/4181/spectrum8khz.png

底部出现的噪声在整个图形宽度上闪烁。高柱保持在位置上,只是幅度略微闪烁。

当我使用一个从1kHz慢慢移动到20kHz的测试声音时,看起来像下面这样(大约在2-3kHz):

http://img846.imageshack.us/img846/7373/spectrum3khz1khz20khz.png

峰值从左到右移动,每个峰值都稍微快一点,所以随着时间的推移,峰值之间的距离增加。看不见的是,峰值回来并从右边离开屏幕(但幅度更小)。此外,所有峰值汇聚成一个略大于屏幕0.5的大峰值。

这是我用来检索数据的代码:

for (int i = 1; i < n / 2; i++) {           
        byte rfk = mRawSpecData[2*i];
        byte ifk = mRawSpecData[2*i+1];
        float magnitude = (float)Math.sqrt(rfk * rfk + ifk * ifk);

        mFormattedSpecData[i-1] = magnitude / 128f;
}

在上面的代码中,mRawSpecData是Visualisers getFFT()函数的结果。捕获数据的长度为1024。目前斜率从1开始,因为mRawSpecData[0]包含DC,mRawSpecData[1]包含n/2。
为了解决我的问题,我还尝试了一些有关频率-bin的DC和相位的调整。想着也许需要对幅度应用某些计算来“清理”图形。但我没有成功(也许是因为我完全不知道DC/相位在发生什么!)。
我花了两个星期在晚上搜索谷歌并尝试不同的计算,但什么都没有真正帮助到我。
那么问题是什么?我做错了什么或者遗漏了什么吗? 之后另一个困扰我的问题是如何正确地缩放幅度。 我的目标是获得0f和1f之间的值。
非常感谢
rampage 附言:屏幕截图是通过运行Android 2.3的手机从eclipse中进行的。
再附言:我还使用了其他各种播放器(如winamp)检查声音,在那里我看到了频谱的正确行为。

在进行FFT之前,您应用的是什么类型的窗函数? - Paul R
@Paul 我自己不应用窗口函数。可视化器连接到 Android 中现有的音频会话。之后,我只设置要使用的 captureSize(在上面的代码中为 n),并调用 visualiser.getFFT(mRawSpecData)。然后该数组将被填充数据。我链接的这两个问题提供了有关 Visualiser 类和 getFFT 方法的更多信息。 - rampage
1
@Rampage:如果没有窗函数,由于时间域数据的隐式矩形窗口化,您的频谱将出现严重的伪像。 - Paul R
@Paul:似乎我无法将任何窗口函数传递给 Visualiser,并且我怀疑在移动设备上实现 Java 中的 FFT 对于实时渲染来说速度是否足够快。我能否对FFT数据进行任何后处理以弥补这些伪像? - rampage
@rampage:在将时域数据传递给FFT例程之前,您需要对其应用窗函数。虽然我不熟悉您使用的具体FFT软件包,但请检查文档以查看是否内置了某种窗函数选项。 - Paul R
1个回答

5
我使用以下计算方法获得了像这张图片一样的可视化效果。我使用了近19张图像来显示这个效果。这些图像是通过getFFT()方法输入响应的。请确保同时启用均衡器和可视化器,否则会产生高值,并且fft值会响应设备音量。实际上,由于我的英语不好,我无法对代码进行解释,因此我在此粘贴了代码。

VisualizerView:

public class VisualizerView extends View {

    private byte[] mBytes;
    private float[] mPoints;

    Paint mForePaint = new Paint();
//  private int width;// height;
    private Paint mPaint;
    Bitmap mBmpArray[];
    int wTilesize;
    int hTilesize;
    int no_of_colomuns;
    private Bitmap peakBitmap;
    private float changeFromTop, changeFromLeft;
    private int images_drawn_starting_point ;
    int magnitudePoints[];

    int max[] = new int[34];
    int temp[]=new int[32];

    private final int[][] images = {
            { R.drawable.blue_fade_1, R.drawable.blue_fade_2,
                    R.drawable.blue_fade_3, R.drawable.blue_fade_4,
                    R.drawable.blue_fade_5, R.drawable.blue_fade_6,
                    R.drawable.blue_fade_7, R.drawable.blue_fade_8,
                    R.drawable.blue_fade_9, R.drawable.blue_fade_10,
                    R.drawable.blue_fade_11, R.drawable.blue_fade_12,
                    R.drawable.blue_fade_13, R.drawable.blue_fade_14,
                    R.drawable.blue_fade_15, R.drawable.blue_fade_16,
                    R.drawable.blue_fade_17, R.drawable.blue_fade_18,
                    R.drawable.blue_fade_19 }};

    private final int IMAGES_LENTH = 19;

    public VisualizerView(Context context) {
        super(context);
        mBmpArray = new Bitmap[20];

        init();

    }

    public VisualizerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        images_drawn_starting_point = h;
        int temp;
        wTilesize = w / 34;
        // hTilesize = h / 30;
        temp = h - ((IMAGES_LENTH - 1) );
        hTilesize = temp / (IMAGES_LENTH );
        no_of_colomuns = ( w / (wTilesize));
        magnitudePoints = new int[no_of_colomuns];
        changeFromLeft = wTilesize + 3f;//For spacing left
        changeFromTop = hTilesize + 2.5f;//For spacing Right
    }
    public void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(5f);

    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        int h = canvas.getHeight();
        int w = canvas.getWidth();
        canvas.drawRect(new Rect(0, 0, w, h), mPaint);

        if (mBytes == null) {
            return;
        }
        if (mPoints == null || mPoints.length < mBytes.length * 4) {
            mPoints = new float[mBytes.length * 4];
        }


        double magnitude;

        //VisualizerActivity.THEME_COLOR=0
        for (int j = 0; j < IMAGES_LENTH; j++)
            loadTile(j,getResources().getDrawable(images[VisualizerActivity.THEME_COLOR][j]));



        for (int i = 0; i < no_of_colomuns; i++) {
            byte rfk = mBytes[2 * i];
            byte ifk = mBytes[2 * i + 1];

            magnitude = ((rfk * rfk + ifk * ifk));
            int dbValue = (int) (10 * Math.log10(magnitude));
            magnitude = Math.round(dbValue * 8);
            try {
                magnitudePoints[i] = (int) magnitude;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        int left;
        int top;
        int index;
        try {
            index = 0;
            left = 0;
            int m = 1;

            if (VisualizerActivity.THEME_STYLE == 0) 
            {
                // common
                for (int i = 0; i < no_of_colomuns; i++) {
                    top = images_drawn_starting_point;
                    index = 18;
                    for (int j = 0; j < IMAGES_LENTH; j++) {
                        if (j > magnitudePoints[m] / IMAGES_LENTH)
                        {
                            canvas.drawBitmap(mBmpArray[0], left, top, mPaint);
                            index++;
                        }
                        else 
                        {
                            canvas.drawBitmap(mBmpArray[index--], left, top,
                                    mPaint);
                        }
                        top -= changeFromTop;// hTilesize+1.5;
                    }
                    m++;
                    left += changeFromLeft;// wTilesize+2.5;

                }
            }

        } catch (Exception e) 
        {
            e.getMessage();
        }
    }

    public void loadTile(int key, Drawable tile) {

        try {
            Bitmap bitmap = Bitmap.createBitmap(wTilesize, hTilesize,
                    Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            tile.setBounds(0, 0, wTilesize, hTilesize);
            tile.draw(canvas);
            mBmpArray[key] = bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void updateVisualizerWithFft(byte[] bytes) {

        if (AudioPlayer.player != null) {
            if (AudioPlayer.player.isPlaying()) {
                mBytes = bytes;
            }
        }
        invalidate();

    }

}

在VisualizerActivity.java文件中:
AudioPlayer.mVisualizer.setCaptureSize(Visualizer
                    .getCaptureSizeRange()[1]);
            AudioPlayer.mVisualizer.setDataCaptureListener(
                    new Visualizer.OnDataCaptureListener() {
                        public void onWaveFormDataCapture(
                                Visualizer visualizer, byte[] bytes,
                                int samplingRate) {
                            // mVisualizerView.updateVisualizer(bytes);
                        }

                        public void onFftDataCapture(Visualizer visualizer,
                                byte[] bytes, int samplingRate) {

                            mVisualizerView.updateVisualizerWithFft(bytes);
                        }
                    }, Visualizer.getMaxCaptureRate() / 2, false, true);

谢谢,这使输出变得更好了!我刚刚检查了你提到的音量问题。你说启用均衡器可以使数据音量独立,是什么意思? - rampage
@Ramesh,你代码中的blue_fade_* drawables是什么? - AndroidDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接