5

Sto provando a creare un'applicazione che esegue un FFT sui dati del microfono, quindi posso esaminare ad es. la più alta frequenza nell'input.Passare dati AVCaptureAudioDataOutput in vDSP/Accelerate.framework

Vedo che ci sono molti metodi per ottenere l'ingresso audio (RemoteIO AudioUnit, AudioQueue services e AVFoundation) ma sembra che AVFoundation sia il più semplice. Ho questa impostazione:

// 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]; 

(più errore di controllo, omesso qui per la leggibilità).

Poi ho implementare il AVCaptureAudioDataOutputSampleBufferDelegate seguente metodo:

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

io sono sicuro quello che il prossimo passo dovrebbe essere. Che cosa descrive esattamente il formato CMSampleBuffer (e quali ipotesi possono essere fatte al riguardo, se ce ne sono)? Come devo ottenere i dati audio grezzi in vDSP_fft_zrip con il minor numero possibile di pre-elaborazione extra? (Inoltre, cosa consiglieresti di fare per verificare che i dati grezzi che vedo siano corretti?)

risposta

6

Il CMSampleBufferRef è un tipo opaco che contiene 0 o più campioni di supporti. C'è un po 'di blurb nei documenti:

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

In questo caso conterrà un buffer audio, nonché la descrizione di formato di campione e le informazioni di temporizzazione e così via. Se sei davvero interessato basta inserire un punto di interruzione nel callback del delegato e dare un'occhiata.

Il primo passo è quello di ottenere un puntatore al buffer di dati che è stato restituito:

// 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); 

Il formato di campionamento predefinito per il microfono iPhone è PCM lineare, con campioni di 16 bit. Questo può essere mono o stereo a seconda se c'è un microfono esterno o meno. Per calcolare la FFT abbiamo bisogno di avere un vettore float. Fortunatamente c'è una funzione di accelerare per fare la conversione per noi:

// 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 
} 

Ora avete un galleggiante vettore del buffer campione che è possibile utilizzare con vDSP_fft_zrip. Non sembra possibile cambiare il formato di input dal microfono per far fluttuare i campioni con AVFoundation, quindi sei bloccato con questo ultimo passaggio di conversione. Praticamente manterrei i buffer, li allineeremo se necessario quando arriva un buffer più grande, in modo che non si stia effettuando il mallocing e liberando i buffer con ogni callback delegato.

Per quanto riguarda la tua ultima domanda, immagino che il modo più semplice per farlo sarebbe quello di iniettare un input conosciuto e controllare che ti dia la risposta corretta. Potresti suonare un'onda sinusoidale nel microfono e controllare che il tuo FFT avesse un picco nel corretto bin di frequenza, qualcosa del genere.

+0

"Il formato audio predefinito per il microfono iPhone è un singolo canale di numeri interi a 16 bit" - da dove viene questa informazione viene? Sono preoccupato che fare ipotesi del genere in generale non siano sicure su hardware di dispositivi diversi. – jtbandes

+0

Hai ragione, e l'ipotesi era in realtà falsa comunque, l'ho aggiornata per verificare il formato audio. Alcuni commenti qui sui valori predefiniti di AVCapture: http://developer.apple.com/library/ios/#samplecode/AVCaptureToAudioUnit/Listings/CaptureSessionController_mm.html – Tark

1

Io non suggerisco di usare AVFoundation per 3 motivi:

  1. L'ho usato per un po 'di applicazioni miniera (morsedec, irtty), funziona bene sul simulatore e in alcuni hardware, ma in altri totalmente fallito!
  2. non si dispone di un buon controllo della frequenza di campionamento di un formato.
  3. latenza potrebbe essere alta.

Suggerisco di iniziare con il codice di esempio aurioTouch di Apple. Per rendere FFT è possibile passare al framework vDSP utilizzando un buffer circolare (I LOVE https://github.com/michaeltyson/TPCircularBuffer).

Spero che questo aiuto