2011-01-25 10 views
8

Sto provando ad estrarre campioni PCM grezzi da un MP3 nella libreria dell'iPod in modo che possa riprodurre la song e manipolare il pitch, il tempo e applicare effetti sonori (come filtri). Ho già seguito il percorso di AVPlayer e AVAudioPlayer, che non consentono affatto il controllo della riproduzione.Estrarre gli esempi di PCM raw della libreria dell'iPod e suonare con gli effetti sonori

Il codice seguente è il massimo che ho ottenuto con questo. Ora sono a un punto in cui non so cosa fare con il CMSampleBufferRef nel mio ciclo while perché non conosco quale framework utilizzare per riprodurre l'audio e applicare tali effetti.

Qualche idea quale sarebbe l'approccio migliore per raggiungere questo obiettivo? Ho esaminato i casi in cui il file viene convertito utilizzando un AVAssetWriter, ma questo non lo taglierà perché il processo richiede troppo tempo. Sicuramente posso solo leggere gli esempi PCM in memoria per la riproduzione senza doverli prima scrivere sul disco?

NB: So che il codice qui sotto fa riferimento a un mp3 all'interno del progetto, ma sono consapevole che questo approccio funzionerà lo stesso come se stessi tirando un NSURL dal MPMediaPropertyAssetURL

 

-(IBAction)loadTrack:(id)sender { 

NSString *songPath = [[NSBundle mainBundle] pathForResource:@"Smooth_Sub Focus_192" ofType:@"mp3"]; 
NSURL *assetURL = [[NSURL alloc] initFileURLWithPath:songPath]; 

AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil]; 

NSError *assetError = nil; 
AVAssetReader *assetReader = [[AVAssetReader assetReaderWithAsset:songAsset 
       error:&assetError] retain]; 
if (assetError) { 
    NSLog (@"Error: %@", assetError); 
    return; 
} 

AVAssetReaderOutput *assetReaderOutput = [[AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks 
          audioSettings: nil] retain]; 
if (![assetReader canAddOutput:assetReaderOutput]) { 
    NSLog (@"Incompatible Asser Reader Output"); 
    return; 
} 

[assetReader addOutput: assetReaderOutput]; 
[assetReader startReading]; 

CMSampleBufferRef nextBuffer; 
while (nextBuffer = [assetReaderOutput copyNextSampleBuffer]) { 
    /* What Do I Do Here? */ 
} 

[assetReader release]; 
[assetReaderOutput release]; 

} 
 

risposta

8

sto facendo qualcosa simile nel mio codice. Il metodo seguente restituisce un po 'di NSData per un AVURLAsset:

- (NSData *)extractDataForAsset:(AVURLAsset *)songAsset { 

    NSError * error = nil; 
    AVAssetReader * reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error]; 

    AVAssetTrack * songTrack = [songAsset.tracks objectAtIndex:0]; 
    AVAssetReaderTrackOutput * output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:nil]; 
    [reader addOutput:output]; 
    [output release]; 

    NSMutableData * fullSongData = [[NSMutableData alloc] init]; 
    [reader startReading]; 

    while (reader.status == AVAssetReaderStatusReading){ 

     AVAssetReaderTrackOutput * trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0]; 
     CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer]; 

     if (sampleBufferRef){ 
      CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef); 

      size_t length = CMBlockBufferGetDataLength(blockBufferRef); 
      UInt8 buffer[length]; 
      CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer); 

      NSData * data = [[NSData alloc] initWithBytes:buffer length:length]; 
      [fullSongData appendData:data]; 
      [data release]; 

      CMSampleBufferInvalidate(sampleBufferRef); 
      CFRelease(sampleBufferRef); 
     } 
    } 

    if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown){ 
     // Something went wrong. Handle it. 
    } 

    if (reader.status == AVAssetReaderStatusCompleted){ 
     // You're done. It worked. 
    } 

    [reader release]; 

    return [fullSongData autorelease]; 
} 

lo consiglio a fare questo su un thread in background, perché è in termini di tempo.

Uno svantaggio di questo metodo è che l'intera song viene caricata in memoria, che è ovviamente limitata.

+1

eccellente. Questo mi porta a metà strada lì. Ho fatto qualche ricerca sui framework audio e sembra che l'unico framework adatto alle mie esigenze sia OpenAL. Con una curva di apprendimento ripida, questo è leggermente offuscante, ma potrei usare NSData con qualcosa come Finch? – Dino

+0

Non ho mai usato Finch da solo, quindi non sono sicuro che accetti direttamente NSData. –

+0

FYI: L'ho provato, e ho ottenuto EXC_BAD_ACCESS ... Potrei rimuoverlo controllando l'OSStatus restituito da 'CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer' prima di fare il' for'. – Larme

2

Penso che si desidera questo lì per garantire la sua PCM ...

NSDictionary* outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys: 

         [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey, 
        //  [NSNumber numberWithInt:44100.0],AVSampleRateKey, /*Not Supported*/ 
        //  [NSNumber numberWithInt: 2],AVNumberOfChannelsKey, /*Not Supported*/ 

         [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey, 
         [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey, 
         [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey, 
         [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved, 

         nil]; 

AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict]; 
5

In aggiunta alla risposta di Tom Irving, vi suggerisco di sostituire

 UInt8 buffer[length]; 
     CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer); 

     NSData * data = [[NSData alloc] initWithBytes:buffer length:length]; 

con

 NSMutableData * data = [[NSMutableData alloc] initWithLength:length]; 
     CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes); 

che evita la doppia gestione dei campioni e riduce il sovraccarico dell'utilizzo della memoria.

in alternativa, è possibile eseguire il wrap [NSMutableData dataWithLength: length] in un pool di rilascio automatico come dimostrato in this answer in una domanda non correlata ma simile.

+0

Fantastico - quando l'ho messo insieme stavo solo cercando di farlo funzionare, non avevo ancora iniziato l'ottimizzazione. Il mio più grande fastidio è che non riesco a trovare un modo per ottenere l'intera dimensione della canzone in byte prima di leggere l'intera cosa in memoria. –

+0

Ovviamente potrei leggerlo una volta, senza copiare nulla, solo ottenere la lunghezza per ogni campione e sommarle, ma sembra troppo disordinato. –

0

Per la durata del brano, io credo che si possa semplicemente interrogare l'Asset canzone così:

float songTimeInSeconds = CMTimeGetSeconds(songAsset.duration); 
    int songMinutes = (int)(songTimeInSeconds/60.); 
    int songSeconds = (int)(songTimeInSeconds - 60.0*songMinutes); 
+0

Perché il downvote? –

+0

Sì, perché? Funziona nel mio codice ... –

Problemi correlati