将 AVCaptureAudioDataOutput 数据传递到 vDSP / Accelerate.framework
Posted
技术标签:
【中文标题】将 AVCaptureAudioDataOutput 数据传递到 vDSP / Accelerate.framework【英文标题】:Passing AVCaptureAudioDataOutput data into vDSP / Accelerate.framework 【发布时间】:2012-12-30 04:19:36 【问题描述】:我正在尝试创建一个对麦克风数据运行 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
? (另外,您建议如何验证我看到的原始数据是否正确?)
【问题讨论】:
【参考方案1】: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 在正确的频率范围内是否有一个峰值,类似这样。
【讨论】:
“iPhone 麦克风的默认音频格式是 16 位整数的单通道”——这些信息从何而来?我担心在不同的设备硬件上做出这样的假设通常是不安全的。 你是对的,无论如何假设实际上是错误的,我已经更新以检查音频格式。这里有一些关于 AVCapture 默认值的 cmets:developer.apple.com/library/ios/#samplecode/…【参考方案2】:我不建议使用 AVFoundation 有 3 个原因:
-
我将它用于我的一些应用程序(morsedec、irtty),它在模拟器和某些硬件上运行良好,但在其他应用程序中完全失败!
您对格式的采样率没有很好的控制。
延迟可能很高。
我建议从苹果的示例代码aurioTouch 开始。 要进行 FFT,您可以使用循环缓冲区转移到 vDSP 框架(我喜欢 https://github.com/michaeltyson/TPCircularBuffer)。
希望有帮助
【讨论】:
以上是关于将 AVCaptureAudioDataOutput 数据传递到 vDSP / Accelerate.framework的主要内容,如果未能解决你的问题,请参考以下文章
Javascript 将正则表达式 \\n 替换为 \n,将 \\t 替换为 \t,将 \\r 替换为 \r 等等