我对mac osx的FFT分析感兴趣。我找到了iOS教程(http://demetrimiller.com/2011/01/25/pitch-detection-in-ios-4-x/)并尝试将其“移植”到OSX。
我创建了一个新的Xcode-Project,集成了代码并删除了可见的错误。虽然不起作用:(
我尝试了很多关于AudioUnit部分的修改,但它没有帮助。
有谁知道出了什么问题? 非常感谢你:)
myView.h
#import <Cocoa/Cocoa.h>
@class RIOInterface;
@interface myView : NSView {
BOOL isListening;
RIOInterface *rioRef;
NSMutableString *key;
float currentFrequency;
NSString *prevChar;
}
@property(nonatomic, retain) NSMutableString *key;
@property(nonatomic, retain) NSString *prevChar;
@property(readwrite) RIOInterface *rioRef;
@property(nonatomic, assign) float currentFrequency;
@property(assign) BOOL isListening;
#pragma mark Listener Controls
- (IBAction)toggleListening:(id)sender;
- (void)startListener;
- (void)stopListener;
- (void)frequencyChangedWithValue:(float)newFrequency;
- (void)updateFrequencyLabel;
@end
myView.m
#import "myView.h"
#import "RIOInterface.h"
@implementation myView
@synthesize key;
@synthesize prevChar;
@synthesize isListening;
@synthesize rioRef;
@synthesize currentFrequency;
#pragma mark -
#pragma mark Listener Controls
- (void)toggleListening {
if (isListening) {
[self stopListener];
} else {
[self startListener];
}
isListening = !isListening;
}
- (void)startListener {
[rioRef startListening:self];
}
- (void)stopListener {
[rioRef stopListening];
}
#pragma mark -
#pragma mark Lifecycle
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
rioRef = [RIOInterface sharedInstance];
[self toggleListening];
}
-(id)initWithCoder:(NSCoder *)coder
{
if (self = [super initWithCoder:coder]) {
[self viewDidLoad];
}
return self;
}
@end
RIOInterface.h
#import <Foundation/Foundation.h>
#import <AudioUnit/AudioUnit.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <Accelerate/Accelerate.h>
#include <stdlib.h>
@class myView;
/**
* This is a singleton class that manages all the low level CoreAudio/RemoteIO
* elements. A singleton is used since we should never be instantiating more
* than one instance of this class per application lifecycle.
*/
#define kInputBus 1
#define kOutputBus 0
#define kBufferSize 1024
#define kBufferCount 1
#define N 10
@interface RIOInterface : NSObject {
NSView *selectedViewController;
myView *listener;
AUGraph processingGraph;
AudioUnit ioUnit;
AudioBufferList* bufferList;
AudioStreamBasicDescription streamFormat;
FFTSetup fftSetup;
COMPLEX_SPLIT A;
int log2n, n, nOver2;
void *dataBuffer;
float *outputBuffer;
size_t bufferCapacity; // In samples
size_t index; // In samples
float sampleRate;
float frequency;
}
@property(nonatomic, assign) id<AVAudioPlayerDelegate> audioPlayerDelegate;
@property(readwrite) myView *listener;
@property(assign) float sampleRate;
@property(assign) float frequency;
#pragma mark Lifecycle
#pragma mark Audio Session/Graph Setup
- (void)initializeAudioSession;
- (void)createAUProcessingGraph;
- (size_t)ASBDForSoundMode;
#pragma mark Playback Controls
- (void)startPlayback;
- (void)startPlaybackFromEncodedArray:(NSArray *)encodedArray;
- (void)stopPlayback;
- (void)printASBD:(AudioStreamBasicDescription)asbd;
#pragma mark Listener Controls
- (void)startListening:(myView*)aListener;
- (void)stopListening;
#pragma mark Generic Audio Controls
- (void)initializeAndStartProcessingGraph;
- (void)stopProcessingGraph;
#pragma mark Singleton Methods
+ (RIOInterface *)sharedInstance;
@end
RIOInterface.m
#import "RIOInterface.h"
#import "CAStreamBasicDescription.h"
#import "CAXException.h"
#import "myView.h"
@implementation RIOInterface
@synthesize listener;
@synthesize audioPlayerDelegate;
@synthesize sampleRate;
@synthesize frequency;
float MagnitudeSquared(float x, float y);
void ConvertInt16ToFloat(RIOInterface* THIS, void *buf, float *outputBuf, size_t capacity);
#pragma mark -
#pragma mark Lifecycle
- (void)dealloc {
if (processingGraph) {
AUGraphStop(processingGraph);
}
}
#pragma mark -
#pragma mark Playback Controls
- (void)startPlayback {
[self createAUProcessingGraph];
[self initializeAndStartProcessingGraph];
//AudioOutputUnitStart(ioUnit);
}
- (void)startPlaybackFromEncodedArray:(NSArray *)encodedArray {
// TODO: once we have our generated array, set up the timer to
// play the encoded array correctly.
}
- (void)stopPlayback {
AUGraphStop(processingGraph);
//AudioOutputUnitStop(ioUnit);
}
#pragma mark -
#pragma mark Listener Controls
- (void)startListening:(myView*)aListener {
self.listener = aListener;
[self createAUProcessingGraph];
[self initializeAndStartProcessingGraph];
}
- (void)stopListening {
[self stopProcessingGraph];
}
#pragma mark -
#pragma mark Generic Audio Controls
- (void)initializeAndStartProcessingGraph {
OSStatus result = AUGraphInitialize(processingGraph);
if (result >= 0) {
AUGraphStart(processingGraph);
} else {
XThrow(result, "error initializing processing graph");
}
}
- (void)stopProcessingGraph {
AUGraphStop(processingGraph);
}
#pragma mark -
#pragma mark Audio Rendering
OSStatus RenderFFTCallback (void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
RIOInterface* THIS = (__bridge RIOInterface *)inRefCon;
COMPLEX_SPLIT A = THIS->A;
void *dataBuffer = THIS->dataBuffer;
float *outputBuffer = THIS->outputBuffer;
FFTSetup fftSetup = THIS->fftSetup;
uint32_t log2n = THIS->log2n;
uint32_t n = THIS->n;
uint32_t nOver2 = THIS->nOver2;
uint32_t stride = 1;
int bufferCapacity = THIS->bufferCapacity;
SInt16 index = THIS->index;
AudioUnit rioUnit = THIS->ioUnit;
OSStatus renderErr;
UInt32 bus1 = 1;
renderErr = AudioUnitRender(rioUnit, ioActionFlags,
inTimeStamp, bus1, inNumberFrames, THIS->bufferList);
if (renderErr < 0) {
return renderErr;
}
// Fill the buffer with our sampled data. If we fill our buffer, run the
// fft.
int read = bufferCapacity - index;
if (read > inNumberFrames) {
memcpy((SInt16 *)dataBuffer + index, THIS->bufferList->mBuffers[0].mData, inNumberFrames*sizeof(SInt16));
THIS->index += inNumberFrames;
} else {
// If we enter this conditional, our buffer will be filled and we should
// perform the FFT.
memcpy((SInt16 *)dataBuffer + index, THIS->bufferList->mBuffers[0].mData, read*sizeof(SInt16));
// Reset the index.
THIS->index = 0;
/*************** FFT ***************/
// We want to deal with only floating point values here.
ConvertInt16ToFloat(THIS, dataBuffer, outputBuffer, bufferCapacity);
/**
Look at the real signal as an interleaved complex vector by casting it.
Then call the transformation function vDSP_ctoz to get a split complex
vector, which for a real signal, divides into an even-odd configuration.
*/
vDSP_ctoz((COMPLEX*)outputBuffer, 2, &A, 1, nOver2);
// Carry out a Forward FFT transform.
vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_FORWARD);
// The output signal is now in a split real form. Use the vDSP_ztoc to get
// a split real vector.
vDSP_ztoc(&A, 1, (COMPLEX *)outputBuffer, 2, nOver2);
// Determine the dominant frequency by taking the magnitude squared and
// saving the bin which it resides in.
float dominantFrequency = 0;
int bin = -1;
for (int i=0; i<n; i+=2) {
float curFreq = MagnitudeSquared(outputBuffer[i], outputBuffer[i+1]);
if (curFreq > dominantFrequency) {
dominantFrequency = curFreq;
bin = (i+1)/2;
}
}
memset(outputBuffer, 0, n*sizeof(SInt16));
printf("Dominant frequency: %f bin: %d \n", bin*(THIS->sampleRate/bufferCapacity), bin);
}
NSLog(@"hh");
return noErr;
}
float MagnitudeSquared(float x, float y) {
return ((x*x) + (y*y));
}
void ConvertInt16ToFloat(RIOInterface* THIS, void *buf, float *outputBuf, size_t capacity) {
AudioConverterRef converter;
OSStatus err;
size_t bytesPerSample = sizeof(float);
AudioStreamBasicDescription outFormat = {0};
outFormat.mFormatID = kAudioFormatLinearPCM;
outFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
outFormat.mBitsPerChannel = 8 * bytesPerSample;
outFormat.mFramesPerPacket = 1;
outFormat.mChannelsPerFrame = 1;
outFormat.mBytesPerPacket = bytesPerSample * outFormat.mFramesPerPacket;
outFormat.mBytesPerFrame = bytesPerSample * outFormat.mChannelsPerFrame;
outFormat.mSampleRate = THIS->sampleRate;
const AudioStreamBasicDescription inFormat = THIS->streamFormat;
UInt32 inSize = capacity*sizeof(SInt16);
UInt32 outSize = capacity*sizeof(float);
err = AudioConverterNew(&inFormat, &outFormat, &converter);
err = AudioConverterConvertBuffer(converter, inSize, buf, &outSize, outputBuf);
}
/* Setup our FFT */
- (void)realFFTSetup {
UInt32 maxFrames = 2048;
dataBuffer = (void*)malloc(maxFrames * sizeof(SInt16));
outputBuffer = (float*)malloc(maxFrames *sizeof(float));
log2n = log2f(maxFrames);
n = 1 << log2n;
assert(n == maxFrames);
nOver2 = maxFrames/2;
bufferCapacity = maxFrames;
index = 0;
A.realp = (float *)malloc(nOver2 * sizeof(float));
A.imagp = (float *)malloc(nOver2 * sizeof(float));
fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
}
#pragma mark -
#pragma mark Audio Session/Graph Setup
// Sets up the audio session based on the properties that were set in the init
// method.
- (void)initializeAudioSession {
NSError *err = nil;
// After activation, update our sample rate. We need to update because there
// is a possibility the system cannot grant our request.
[self realFFTSetup];
}
// This method will create an AUGraph for either input or output.
// Our application will never perform both operations simultaneously.
- (void)createAUProcessingGraph {
OSStatus err;
// Configure the search parameters to find the default playback output unit
// (called the kAudioUnitSubType_RemoteIO on iOS but
// kAudioUnitSubType_DefaultOutput on Mac OS X)
AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;
// Declare and instantiate an audio processing graph
NewAUGraph(&processingGraph);
// Add an audio unit node to the graph, then instantiate the audio unit.
/*
An AUNode is an opaque type that represents an audio unit in the context
of an audio processing graph. You receive a reference to the new audio unit
instance, in the ioUnit parameter, on output of the AUGraphNodeInfo
function call.
*/
AUNode ioNode;
AUGraphAddNode(processingGraph, &ioUnitDescription, &ioNode);
AUGraphOpen(processingGraph); // indirectly performs audio unit instantiation
// Obtain a reference to the newly-instantiated I/O unit. Each Audio Unit
// requires its own configuration.
AUGraphNodeInfo(processingGraph, ioNode, NULL, &ioUnit);
// Initialize below.
AURenderCallbackStruct callbackStruct = {0};
UInt32 enableInput;
UInt32 enableOutput;
// Enable input and disable output.
enableInput = 1; enableOutput = 0;
callbackStruct.inputProc = RenderFFTCallback;
err = AudioUnitSetProperty(ioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
kInputBus, &enableInput, sizeof(enableInput));
err = AudioUnitSetProperty(ioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
kOutputBus, &enableOutput, sizeof(enableOutput));
err = AudioUnitSetProperty(ioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Input,
kOutputBus, &callbackStruct, sizeof(callbackStruct));
// Set the stream format.
size_t bytesPerSample = [self ASBDForSoundMode];
err = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
kInputBus, &streamFormat, sizeof(streamFormat));
err = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
kOutputBus, &streamFormat, sizeof(streamFormat));
// Disable system buffer allocation. We'll do it ourselves.
UInt32 flag = 0;
err = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
kInputBus, &flag, sizeof(flag));
// Allocate AudioBuffers for use when listening.
// TODO: Move into initialization...should only be required once.
bufferList = (AudioBufferList *)malloc(sizeof(AudioBuffer));
bufferList->mNumberBuffers = 1;
bufferList->mBuffers[0].mNumberChannels = 1;
bufferList->mBuffers[0].mDataByteSize = kBufferSize*bytesPerSample;
bufferList->mBuffers[0].mData = calloc(kBufferSize, bytesPerSample);
}
// Set the AudioStreamBasicDescription for listening to audio data. Set the
// stream member var here as well.
- (size_t)ASBDForSoundMode {
AudioStreamBasicDescription asbd = {0};
size_t bytesPerSample;
bytesPerSample = sizeof(SInt16);
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
asbd.mBitsPerChannel = 8 * bytesPerSample;
asbd.mFramesPerPacket = 1;
asbd.mChannelsPerFrame = 1;
asbd.mBytesPerPacket = bytesPerSample * asbd.mFramesPerPacket;
asbd.mBytesPerFrame = bytesPerSample * asbd.mChannelsPerFrame;
asbd.mSampleRate = sampleRate;
streamFormat = asbd;
[self printASBD:streamFormat];
return bytesPerSample;
}
#pragma mark -
#pragma mark Utility
- (void)printASBD:(AudioStreamBasicDescription)asbd {
char formatIDString[5];
UInt32 formatID = CFSwapInt32HostToBig (asbd.mFormatID);
bcopy (&formatID, formatIDString, 4);
formatIDString[4] = '\0';
NSLog (@" Sample Rate: %10.0f", asbd.mSampleRate);
NSLog (@" Format ID: %10s", formatIDString);
NSLog (@" Format Flags: %10lX", asbd.mFormatFlags);
NSLog (@" Bytes per Packet: %10ld", asbd.mBytesPerPacket);
NSLog (@" Frames per Packet: %10ld", asbd.mFramesPerPacket);
NSLog (@" Bytes per Frame: %10ld", asbd.mBytesPerFrame);
NSLog (@" Channels per Frame: %10ld", asbd.mChannelsPerFrame);
NSLog (@" Bits per Channel: %10ld", asbd.mBitsPerChannel);
}
// *************** Singleton *********************
static RIOInterface *sharedInstance = nil;
#pragma mark -
#pragma mark Singleton Methods
+ (RIOInterface *)sharedInstance
{
if (sharedInstance == nil) {
sharedInstance = [[RIOInterface alloc] init];
}
return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [super allocWithZone:zone];
return sharedInstance; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
@end
答案 0 :(得分:0)
您无法在Mac上使用kAudioUnitSubType_DefaultOutput进行麦克风输入。
答案 1 :(得分:0)
- 用于音频单元子类型尝试
kAudioUnitSubType_HALOutput
然而,除非你真的知道如何将iOS音频应用程序移植到OSX,否则我建议通过移植比fft更简单的过程(例如播放)来打破僵局,然后使用学到的实践经验更苛刻的场景。它的问题太复杂,无法适应邮件回复。学习曲线陡峭,没有简短的方法来理解事物的运作方式。要有耐心,这不是一个快速修复的工作。
另一方面,如果您想在Mac上查看FFT的工作示例,也许您也可以看一下:
https://github.com/Sample-Hold/SimpleSpectrumAnalyzer
UU