2011-01-25 17 views
8

que estoy tratando de extraer muestras de PCM en bruto de un MP3 en la biblioteca del iPod para que pueda reproducir la canción y manipular el terreno de juego, el tempo, y aplicar efectos de sonido (como filtros). Ya he bajado la ruta de AVPlayer y AVAudioPlayer, que no permiten mucho control sobre la reproducción.Extraer muestras de PCM en bruto biblioteca del iPod y jugar con los efectos de sonido

El código siguiente es por lo que yo he conseguido con esto. Estoy en un punto en el que no sé qué hacer con los de CMSampleBufferRef en mi bucle de tiempo porque no sé qué marco para utilizar con el fin de reproducir el audio y aplicar tales efectos.

Cualquier idea de cuál sería el mejor método para lograr esto? He visto casos en los que el archivo se convierte utilizando un AVAssetWriter, pero esto no va a ser suficiente porque el proceso consume demasiado tiempo. ¿Debo simplemente leer las muestras de PCM en la memoria para reproducirlas sin tener que escribirlas primero en el disco?

NB: Sé que el código de abajo hace referencia a un mp3 en el proyecto, pero soy consciente de que este enfoque funcionará de la misma como si estuviera sacando una NSURL del 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]; 

} 
 

Respuesta

8

estoy haciendo algo similar en mi propio código. El siguiente método devuelve parte NSData para 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]; 
} 

Yo recomendaría hacer esto en un subproceso en segundo plano, porque es mucho tiempo.

Un inconveniente de este método es que toda la canción se carga en memoria, que es, por supuesto, limitada.

+1

excelente. Esto me lleva a mitad de camino allí. He estado investigando sobre marcos de audio y parece que el único marco que se ajusta a mis requisitos es OpenAL. Con una curva de aprendizaje empinada esto es un poco desagradable, pero ¿podría usar NSData con algo como Finch? – Dino

+0

Nunca he usado Finch, por lo que no estoy seguro si acepta directamente NSData. –

+0

FYI: Yo probé, y me dieron EXC_BAD_ACCESS ... podría quitarlo marcando la OSStatus devuelto por 'CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer' antes de hacer el' for'. – Larme

2

creo que desea que esta allí para asegurar su 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

Adicionalmente a la contestación de Tom Irving, sugiero reemplazar

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

cuales evita el doble manejo de las muestras y reduce el gasto de uso de memoria.

, alternativamente, se puede envolver [NSMutableData dataWithLength: longitud] en una piscina de liberación automática como se ha demostrado in this answer a una pregunta no relacionada pero similar.

+0

Fantástico: cuando lo armé intenté hacerlo funcionar, todavía no había empezado la optimización. Mi mayor molestia es que parece que no puedo encontrar la forma de obtener el tamaño completo de la canción en bytes antes de leer todo en la memoria. –

+0

Obviamente, pude leerlo una vez, sin copiar nada, solo obtengo la longitud de cada muestra y las sumas, pero parece demasiado desordenado. –

0

Para la duración de la canción, creo que simplemente puede consultar el Activo canción así:

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

¿Por qué el voto a favor? –

+0

Sí, ¿por qué? Funciona en mi código ... –

Cuestiones relacionadas