iOS Accelerate框架中vDSP_ctoz的数据应该是啥格式

Posted

技术标签:

【中文标题】iOS Accelerate框架中vDSP_ctoz的数据应该是啥格式【英文标题】:What format should the data be for vDSP_ctoz in iOS Accelerate frameworkiOS Accelerate框架中vDSP_ctoz的数据应该是什么格式 【发布时间】:2014-03-23 00:06:20 【问题描述】:

我正在尝试显示适用于 ios 的频谱分析仪,但两周后卡住了。我在这里阅读了几乎所有关于 FFT 和 Accelerate Frameworks 的文章,并从 Apple 下载了 aurioTouch2 示例。

我想我了解 FFT 的机制(20 年前在 Uni 做过),并且是一个相当有经验的 iOS 程序员,但我碰壁了。

我正在使用 AudioUnit 播放 mp3、m4a 和 wav 文件,并且效果很好。我已将渲染回调附加到 AUGraph,我可以将波形绘制到音乐中。波形与音乐配合得很好。

当我从范围为 0 .. 1 的浮点形式的渲染回调中获取数据并尝试通过 FFT 代码(我自己的或 aurioTouch2 的 FFTBufferManager.mm)传递它时,我得到了一些不完全错误的东西,但也不正确。或者例如这是一个 440Hz 正弦波:

那个峰值是-6.1306,然后是-24。 -31., -35.接近尾声的值在 -63 左右。

“黑贝蒂”的 gif 动画:

Animated gif for "Black Betty

我从 Render 回调收到的格式:

AudioStreamBasicDescription outputFileFormat;
outputFileFormat.mSampleRate = 44100;
outputFileFormat.mFormatID = kAudioFormatLinearPCM;
outputFileFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
outputFileFormat.mBitsPerChannel = 32;
outputFileFormat.mChannelsPerFrame = 2;
outputFileFormat.mFramesPerPacket = 1;
outputFileFormat.mBytesPerFrame = outputFileFormat.mBitsPerChannel / 8;
outputFileFormat.mBytesPerPacket = outputFileFormat.mBytesPerFrame;

在查看 aurioTouch2 示例时,看起来他们正在接收签名 int 格式的数据,但随后运行 AudioConverter 将其转换为浮点数。它们的格式很难破译,但使用的是宏:

    drawFormat.SetAUCanonical(2, false);
    drawFormat.mSampleRate = 44100;

    XThrowIfError(AudioConverterNew(&thruFormat, &drawFormat, &audioConverter), "couldn't setup AudioConverter");

在他们的渲染回调中,他们将数据从 AudioBufferList 复制到 mAudioBuffer (Float32*) 并将其传递给调用 vDSP_ctoz 的 CalculateFFT 方法

    //Generate a split complex vector from the real data
    vDSP_ctoz((COMPLEX *)mAudioBuffer, 2, &mDspSplitComplex, 1, mFFTLength);

我认为这就是我的问题所在。 vDSP_ctoz 期望什么格式?它被转换为 (COMPLEX*) 但我在 aurioTouch2 代码中找不到将 mAudioBuffer 数据转换为 (COMPLEX*) 格式的任何地方。那么一定是来自这种格式的渲染回调吗?

typedef struct DSPComplex 
    float  real;
    float  imag;
 DSPComplex;
typedef DSPComplex                      COMPLEX;

如果我此时没有正确的格式(或理解格式),那么调试其余部分就没有意义了。

任何帮助将不胜感激。

我正在使用的来自 AurioTouch2 的代码:

Boolean FFTBufferManager::ComputeFFTFloat(Float32 *outFFTData)

if (HasNewAudioData())

    // Added after Hotpaw2 comment.
    UInt32 windowSize = mFFTLength;
    Float32 *window = (float *) malloc(windowSize * sizeof(float));

    memset(window, 0, windowSize * sizeof(float));

    vDSP_hann_window(window, windowSize, 0);

    vDSP_vmul( mAudioBuffer, 1, window, 1, mAudioBuffer, 1, mFFTLength);

    // Added after Hotpaw2 comment.
    DSPComplex *audioBufferComplex = new DSPComplex[mFFTLength];

    for (int i=0; i < mFFTLength; i++)
    
        audioBufferComplex[i].real = mAudioBuffer[i];
        audioBufferComplex[i].imag = 0.0f;
    

    //Generate a split complex vector from the real data
    vDSP_ctoz((COMPLEX *)audioBufferComplex, 2, &mDspSplitComplex, 1, mFFTLength);

    //Take the fft and scale appropriately
    vDSP_fft_zrip(mSpectrumAnalysis, &mDspSplitComplex, 1, mLog2N, kFFTDirection_Forward);
    vDSP_vsmul(mDspSplitComplex.realp, 1, &mFFTNormFactor, mDspSplitComplex.realp, 1, mFFTLength);
    vDSP_vsmul(mDspSplitComplex.imagp, 1, &mFFTNormFactor, mDspSplitComplex.imagp, 1, mFFTLength);

    //Zero out the nyquist value
    mDspSplitComplex.imagp[0] = 0.0;

    //Convert the fft data to dB
    vDSP_zvmags(&mDspSplitComplex, 1, outFFTData, 1, mFFTLength);

    //In order to avoid taking log10 of zero, an adjusting factor is added in to make the minimum value equal -128dB
    vDSP_vsadd( outFFTData, 1, &mAdjust0DB, outFFTData, 1, mFFTLength);
    Float32 one = 1;
    vDSP_vdbcon(outFFTData, 1, &one, outFFTData, 1, mFFTLength, 0);

    free( audioBufferComplex);
    free( window);

    OSAtomicDecrement32Barrier(&mHasAudioData);
    OSAtomicIncrement32Barrier(&mNeedsAudioData);
    mAudioBufferCurrentIndex = 0;
    return true;

else if (mNeedsAudioData == 0)
    OSAtomicIncrement32Barrier(&mNeedsAudioData);

return false;

阅读下面的答案后,我尝试将其添加到方法的顶部:

    DSPComplex *audioBufferComplex = new DSPComplex[mFFTLength];

    for (int i=0; i < mFFTLength; i++)
    
        audioBufferComplex[i].real = mAudioBuffer[i];
        audioBufferComplex[i].imag = 0.0f;
    

    //Generate a split complex vector from the real data
    vDSP_ctoz((COMPLEX *)audioBufferComplex, 2, &mDspSplitComplex, 1, mFFTLength);

我得到的结果是这样的:

我现在正在渲染最后的 5 个结果,它们是后面褪色的。

添加hann窗口后:

应用 hann 窗口后现在看起来好多了(感谢 hotpaw2)。不担心镜像。

我现在的主要问题是使用与其他频谱分析器不同的真实歌曲。无论我通过什么音乐,所有东西总是被推到左边。应用窗口后,它似乎更好地适应了节拍。

【问题讨论】:

memset 之前调用vDSP_hann_window 是不需要的。 【参考方案1】:

AU 渲染回调仅返回所需的复杂输入的实部。要使用复数 FFT,您需要自己用零填充相同数量的虚部,并在需要时复制实部的元素。

【讨论】:

说的有道理,我看了之后试了一下,结果还是不对。我得到了 440Hz 声音文件的镜像。所以不是左边只有一个峰(如上图所示),我在左边有一个峰,在右边有一个峰,中间有一个碗形。我已经编辑了答案以显示我从 AurioTouch2 复制的代码。 碗形结果对于具有少量高频内容的严格实输入的复杂 FFT 的全长结果是正确的。它应该是共轭对称的。所以通常只绘制前半部分的两倍。 幅度响应中的肠形也可能是矩形窗口而不是 Von Hann 或正弦曲线上的其他窗口的结果,而不是 FFT 长度中的整数周期。 哦,我忘了给它添加一个窗口。奇怪的是它没有在 AurioTouch2 中完成。它看起来好多了。添加了新的截图。你是一个很大的帮助 hotpaw2。

以上是关于iOS Accelerate框架中vDSP_ctoz的数据应该是啥格式的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 iOS Accelerate 框架正确填充 FFT 的二维数组

iOS Accelerate Framework vImage - 性能改进?

如何使用 Accelerate Framework 将 iOS 相机图像转换为灰度?

使用 Accelerate 框架的对称带矩阵的特征值

iOS Accelerate低通FFT滤波器镜像结果

Accelerate 的 vImage 与 vDSP