5

我正在尝试创建一个对麦克风数据运行 FFT 的应用程序,因此我可以检查例如输入中最响亮的频率。

我看到有很多获取音频输入的方法(RemoteIO AudioUnit、AudioQueue 服务和 AVFoundation),但似乎 AVFoundation 是最简单的。我有这个设置:

// Configure the audio session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryRecord error:NULL];
[session setMode:AVAudioSessionModeMeasurement error:NULL];
[session setActive:YES error:NULL];

// Optional - default gives 1024 samples at 44.1kHz
//[session setPreferredIOBufferDuration:samplesPerSlice/session.sampleRate error:NULL];

// Configure the capture session (strongly-referenced instance variable, otherwise the capture stops after one slice)
_captureSession = [[AVCaptureSession alloc] init];

// Configure audio device input
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:NULL];
[_captureSession addInput:input];

// Configure audio data output
AVCaptureAudioDataOutput *output = [[AVCaptureAudioDataOutput alloc] init];
dispatch_queue_t queue = dispatch_queue_create("My callback", DISPATCH_QUEUE_SERIAL);
[output setSampleBufferDelegate:self queue:queue];
[_captureSession addOutput:output];

// Start the capture session.   
[_captureSession startRunning];

(加上错误检查,为了便于阅读,此处省略)。

然后我实现以下AVCaptureAudioDataOutputSampleBufferDelegate方法:

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"Num samples: %ld", CMSampleBufferGetNumSamples(sampleBuffer));
    // Usually gives 1024 (except the first slice)
}

我不确定下一步应该是什么。格式到底CMSampleBuffer描述了什么(如果有的话,可以对它做出什么假设)?vDSP_fft_zrip我应该如何以尽可能少的额外预处理来获取原始音频数据?(另外,你会建议做什么来验证我看到的原始数据是正确的?)

4

2 回答 2

6

CMSampleBufferRef是包含 0 个或更多媒体样本的不透明类型。文档中有一些简介:

http://developer.apple.com/library/ios/#documentation/CoreMedia/Reference/CMSampleBuffer/Reference/reference.html

在这种情况下,它将包含一个音频缓冲区,以及对样本格式和时序信息的描述等。如果您真的感兴趣,只需在委托回调中放置一个断点并查看一下。

第一步是获取一个指向已返回数据缓冲区的指针:

// get a pointer to the audio bytes
CMItemCount numSamples = CMSampleBufferGetNumSamples(sampleBuffer);
CMBlockBufferRef audioBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t lengthAtOffset;
size_t totalLength;
char *samples;
CMBlockBufferGetDataPointer(audioBuffer, 0, &lengthAtOffset, &totalLength, &samples);

iPhone 麦克风的默认采样格式是线性 PCM,带有 16 位采样。这可能是单声道或立体声,具体取决于是否有外部麦克风。要计算 FFT,我们需要一个浮点向量。幸运的是,有一个加速功能可以为我们进行转换:

// check what sample format we have
// this should always be linear PCM
// but may have 1 or 2 channels
CMAudioFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
const AudioStreamBasicDescription *desc = CMAudioFormatDescriptionGetStreamBasicDescription(format);
assert(desc->mFormatID == kAudioFormatLinearPCM);
if (desc->mChannelsPerFrame == 1 && desc->mBitsPerChannel == 16) {
    float *convertedSamples = malloc(numSamples * sizeof(float));
    vDSP_vflt16((short *)samples, 1, convertedSamples, 1, numSamples);
} else {
    // handle other cases as required
}

现在你有一个样本缓冲区的浮点向量,你可以使用它vDSP_fft_zrip。似乎无法将麦克风的输入格式更改为使用 浮动样本AVFoundation,因此您被最后一个转换步骤所困扰。在实践中,我会保留缓冲区,如果有必要在更大的缓冲区到达时重新分配它们,这样您就不会在每个委托回调中分配和释放缓冲区。

至于你的最后一个问题,我想最简单的方法是注入一个已知的输入并检查它是否给你正确的响应。您可以在麦克风中播放一个正弦波,并检查您的 FFT 在正确的频率箱中是否有一个峰值,类似这样。

于 2012-12-31T12:20:03.620 回答
1

我不建议使用 AVFoundation 有 3 个原因:

  1. 我将它用于我的一些应用程序(morsedec,irtty),它在模拟器和某些硬件上运行良好,但在其他应用程序中完全失败!
  2. 您对格式的采样率没有很好的控制。
  3. 延迟可能很高。

我建议从苹果的示例代码 aurioTouch 开始。要进行 FFT,您可以使用循环缓冲区(我喜欢https://github.com/michaeltyson/TPCircularBuffer)转移到 vDSP 框架。

希望这有帮助

于 2013-02-08T11:08:16.670 回答