Cómo leer correctamente muestras de PCM decodificadas en iOS usando AVAssetReader – deencoding actualmente incorrecta

Actualmente estoy trabajando en una aplicación como parte de mi Licenciatura en Ciencias de la Computación. La aplicación correlacionará los datos del hardware del iPhone (acelerómetro, gps) y la música que se está reproduciendo.

El proyecto todavía está en su infancia, después de haber trabajado en él por solo 2 meses.

El momento en que estoy ahora y en donde necesito ayuda, es leer muestras de PCM de canciones de la biblioteca de iTunes, y reproducirlas usando una unidad de audio y otra. Actualmente, la implementación que me gustaría trabajar hace lo siguiente: elige una canción aleatoria de iTunes y lee muestras de ella cuando es necesario, y almacena en un búfer, llamémosle sampleBuffer. Más tarde, en el model de consumo, la unidad de audio (que tiene un mezclador y una salida remota de la unidad de disco) tiene una callback donde simplemente copio el número requerido de muestras de sampleBuffer en el buffer especificado en la callback. Lo que escucho a través de los altavoces es algo que no es exactamente lo que espero; ¡Puedo reconocer que está tocando la canción sin embargo, parece que está descodificada incorrectamente y tiene mucho ruido! Adjunté una image que muestra el primer ~ medio segundo (24576 samples @ 44.1kHz), y esto no se asemeja a una salida de aspecto normal. Antes de entrar en la list he comprobado que el file no está dañado, similarmente he escrito casos de testing para el búfer (por lo que sé que el búfer no altera las muestras), y aunque esta podría no ser la mejor manera de hacerlo (algunos podrían argumentar que vaya a la ruta de queue de audio), quiero realizar varias manipulaciones en las muestras, así como cambiar la canción antes de que se termine, reorganizar qué canción se reproduce, etc. Además, tal vez haya algunas configuraciones incorrectas en el audio unidad, sin embargo, el gráfico que muestra las muestras (que muestra que las muestras se decodifican incorrectamente) se toma directamente del búfer, por lo que solo busco ahora para resolver por qué la lectura del disco y la desencoding no funcionan correctamente. En este momento simplemente quiero hacer una obra de teatro. No se pueden publicar imágenes porque son nuevas en stackoverflow, por lo que aquí se muestra el enlace a la image: http://i.stack.imgur.com/RHjlv.jpg

Listado:

Aquí es donde configuro el audioReadSettigns que se utilizará para AVAssetReaderAudioMixOutput

// Set the read settings audioReadSettings = [[NSMutableDictionary alloc] init]; [audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey]; [audioReadSettings setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey]; [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey]; [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey]; [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsNonInterleaved]; [audioReadSettings setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey]; 

Ahora, la siguiente list de códigos es un método que recibe un NSString con el persistant_id de la canción:

 -(BOOL)setNextSongID:(NSString*)persistand_id { assert(persistand_id != nil); MPMediaItem *song = [self getMediaItemForPersistantID:persistand_id]; NSURL *assetUrl = [song valueForProperty:MPMediaItemPropertyAssetURL]; AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetUrl options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]]; NSError *assetError = nil; assetReader = [[AVAssetReader assetReaderWithAsset:songAsset error:&assetError] retain]; if (assetError) { NSLog(@"error: %@", assetError); return NO; } CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, songAsset.duration); [assetReader setTimeRange:timeRange]; track = [[songAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; assetReaderOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:[NSArray arrayWithObject:track] audioSettings:audioReadSettings]; if (![assetReader canAddOutput:assetReaderOutput]) { NSLog(@"cant add reader output... die!"); return NO; } [assetReader addOutput:assetReaderOutput]; [assetReader startReading]; // just getting some basic information about the track to print NSArray *formatDesc = ((AVAssetTrack*)[[assetReaderOutput audioTracks] objectAtIndex:0]).formatDescriptions; for (unsigned int i = 0; i < [formatDesc count]; ++i) { CMAudioFormatDescriptionRef item = (CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i]; const CAStreamBasicDescription *asDesc = (CAStreamBasicDescription*)CMAudioFormatDescriptionGetStreamBasicDescription(item); if (asDesc) { // get data numChannels = asDesc->mChannelsPerFrame; sampleRate = asDesc->mSampleRate; asDesc->Print(); } } [self copyEnoughSamplesToBufferForLength:24000]; return YES; } 

A continuación se presenta la function – (void) copyEnoughSamplesToBufferForLength:

 -(void)copyEnoughSamplesToBufferForLength:(UInt32)samples_count { [w_lock lock]; int stillToCopy = 0; if (sampleBuffer->numSamples() < samples_count) { stillToCopy = samples_count; } NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init]; CMSampleBufferRef sampleBufferRef; SInt16 *dataBuffer = (SInt16*)malloc(8192 * sizeof(SInt16)); int a = 0; while (stillToCopy > 0) { sampleBufferRef = [assetReaderOutput copyNextSampleBuffer]; if (!sampleBufferRef) { // end of song or no more samples return; } CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBufferRef); CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBufferRef); AudioBufferList audioBufferList; CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBufferRef, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer); int data_length = floorf(numSamplesInBuffer * 1.0f); int j = 0; for (int bufferCount=0; bufferCount < audioBufferList.mNumberBuffers; bufferCount++) { SInt16* samples = (SInt16 *)audioBufferList.mBuffers[bufferCount].mData; for (int i=0; i < numSamplesInBuffer; i++) { dataBuffer[j] = samples[i]; j++; } } CFRelease(sampleBufferRef); sampleBuffer->putSamples(dataBuffer, j); stillToCopy = stillToCopy - data_length; } free(dataBuffer); [w_lock unlock]; [apool release]; } 

Ahora, el sampleBuffer tendrá muestras descodificadas incorrectamente. ¿Alguien puede ayudarme por qué esto es así? Esto sucede para diferentes files en mi biblioteca de iTunes (mp3, aac, wav, etc.). Cualquier ayuda sería muy apreciada, además, si necesita cualquier otra list de mi código, o tal vez como suena la salida, lo adjuntaré por request. He estado sentado en esto durante la semana pasada intentando depurarlo y no he encontrado ninguna ayuda en línea; todos parecen estar intentándolo en mi path, pero parece que solo tengo este problema.

Gracias por cualquier ayuda en absoluto!

Peter

Actualmente, también estoy trabajando en un proyecto que consiste en extraer muestras de audio de iTunes Library en AudioUnit.

El audiounit render call back se incluye para su reference. El formatting de input se establece como SInt16StereoStreamFormat.

He utilizado la implementación de búfer circular de Michael Tyson: TPCircularBuffer como el almacenamiento intermedio. Muy fácil de usar y entender !!! Gracias michael

 - (void) loadBuffer:(NSURL *)assetURL_ { if (nil != self.iPodAssetReader) { [iTunesOperationQueue cancelAllOperations]; [self cleanUpBuffer]; } NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, [NSNumber numberWithFloat:44100.0], AVSampleRateKey, [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, nil]; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:assetURL_ options:nil]; if (asset == nil) { NSLog(@"asset is not defined!"); return; } NSLog(@"Total Asset Duration: %f", CMTimeGetSeconds(asset.duration)); NSError *assetError = nil; self.iPodAssetReader = [AVAssetReader assetReaderWithAsset:asset error:&assetError]; if (assetError) { NSLog (@"error: %@", assetError); return; } AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings]; if (! [iPodAssetReader canAddOutput: readerOutput]) { NSLog (@"can't add reader output... die!"); return; } // add output reader to reader [iPodAssetReader addOutput: readerOutput]; if (! [iPodAssetReader startReading]) { NSLog(@"Unable to start reading!"); return; } // Init circular buffer TPCircularBufferInit(&playbackState.circularBuffer, kTotalBufferSize); __block NSBlockOperation * feediPodBufferOperation = [NSBlockOperation blockOperationWithBlock:^{ while (![feediPodBufferOperation isCancelled] && iPodAssetReader.status != AVAssetReaderStatusCompleted) { if (iPodAssetReader.status == AVAssetReaderStatusReading) { // Check if the available buffer space is enough to hold at least one cycle of the sample data if (kTotalBufferSize - playbackState.circularBuffer.fillCount >= 32768) { CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer]; if (nextBuffer) { AudioBufferList abl; CMBlockBufferRef blockBuffer; CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer); UInt64 size = CMSampleBufferGetTotalSampleSize(nextBuffer); int bytesCopied = TPCircularBufferProduceBytes(&playbackState.circularBuffer, abl.mBuffers[0].mData, size); if (!playbackState.bufferIsReady && bytesCopied > 0) { playbackState.bufferIsReady = YES; } CFRelease(nextBuffer); CFRelease(blockBuffer); } else { break; } } } } NSLog(@"iPod Buffer Reading Finished"); }]; [iTunesOperationQueue addOperation:feediPodBufferOperation]; } static OSStatus ipodRenderCallback ( void *inRefCon, // A pointer to a struct containing the complete audio data // to play, as well as state information such as the // first sample to play on this invocation of the callback. AudioUnitRenderActionFlags *ioActionFlags, // Unused here. When generating audio, use ioActionFlags to indicate silence // between sounds; for silence, also memset the ioData buffers to 0. const AudioTimeStamp *inTimeStamp, // Unused here. UInt32 inBusNumber, // The mixer unit input bus that is requesting some new // frames of audio data to play. UInt32 inNumberFrames, // The number of frames of audio to provide to the buffer(s) // pointed to by the ioData parameter. AudioBufferList *ioData // On output, the audio data to play. The callback's primary // responsibility is to fill the buffer(s) in the // AudioBufferList. ) { Audio* audioObject = (Audio*)inRefCon; AudioSampleType *outSample = (AudioSampleType *)ioData->mBuffers[0].mData; // Zero-out all the output samples first memset(outSample, 0, inNumberFrames * kUnitSize * 2); if ( audioObject.playingiPod && audioObject.bufferIsReady) { // Pull audio from circular buffer int32_t availableBytes; AudioSampleType *bufferTail = TPCircularBufferTail(&audioObject.circularBuffer, &availableBytes); memcpy(outSample, bufferTail, MIN(availableBytes, inNumberFrames * kUnitSize * 2) ); TPCircularBufferConsume(&audioObject.circularBuffer, MIN(availableBytes, inNumberFrames * kUnitSize * 2) ); audioObject.currentSampleNum += MIN(availableBytes / (kUnitSize * 2), inNumberFrames); if (availableBytes <= inNumberFrames * kUnitSize * 2) { // Buffer is running out or playback is finished audioObject.bufferIsReady = NO; audioObject.playingiPod = NO; audioObject.currentSampleNum = 0; if ([[audioObject delegate] respondsToSelector:@selector(playbackDidFinish)]) { [[audioObject delegate] performSelector:@selector(playbackDidFinish)]; } } } return noErr; } - (void) setupSInt16StereoStreamFormat { // The AudioUnitSampleType data type is the recommended type for sample data in audio // units. This obtains the byte size of the type for use in filling in the ASBD. size_t bytesPerSample = sizeof (AudioSampleType); // Fill the application audio format struct's fields to define a linear PCM, // stereo, noninterleaved stream at the hardware sample rate. SInt16StereoStreamFormat.mFormatID = kAudioFormatLinearPCM; SInt16StereoStreamFormat.mFormatFlags = kAudioFormatFlagsCanonical; SInt16StereoStreamFormat.mBytesPerPacket = 2 * bytesPerSample; // *** kAudioFormatFlagsCanonical <- implicit interleaved data => (left sample + right sample) per Packet SInt16StereoStreamFormat.mFramesPerPacket = 1; SInt16StereoStreamFormat.mBytesPerFrame = SInt16StereoStreamFormat.mBytesPerPacket * SInt16StereoStreamFormat.mFramesPerPacket; SInt16StereoStreamFormat.mChannelsPerFrame = 2; // 2 indicates stereo SInt16StereoStreamFormat.mBitsPerChannel = 8 * bytesPerSample; SInt16StereoStreamFormat.mSampleRate = graphSampleRate; NSLog (@"The stereo stream format for the \"iPod\" mixer input bus:"); [self printASBD: SInt16StereoStreamFormat]; } 

Supongo que es un poco tarde, pero puedes probar esta biblioteca:

https://bitbucket.org/artgillespie/tslibraryimport

Después de usar esto para save el audio en un file, puede procesar los datos con render callbacks de MixerHost.

Si yo fuera tú, utilizaría kAudioUnitSubType_AudioFilePlayer para reproducir el file y acceder a sus muestras con las unidades de render callback.

O

Use ExtAudioFileRef para extraer las muestras directamente a un búfer.