Conversión de música y cómo saber si se completó la escritura

Tuve que convertir una canción de gran tamaño de file de la biblioteca de iTunes a un file de canción más pequeño de 8K.

Como hice el async de conversión, el bool siempre devuelve verdadero aunque no se haya completado la escritura en la carpeta doc. En este momento estoy usando un retraso de 10 segundos antes de que volviera a llamar a la function y funciona bien en el interino para iPhone 5s, pero me gustaría ocuparme de los dispositivos más lentos.

Por favor, dame un puntero / recomendación sobre mi código.

-(void)startUploadSongAnalysis { [self updateProgressYForID3NForUpload:NO]; if ([self.uploadWorkingAray count]>=1) { Song *songVar = [self.uploadWorkingAray objectAtIndex:0];//core data var NSLog(@"songVar %@",songVar.songName); NSLog(@"songVar %@",songVar.songURL); NSURL *songU = [NSURL URLWithString:songVar.songURL]; //URL of iTunes Lib // self.asset = [AVAsset assetWithURL:songU]; // NSLog(@"asset %@",self.asset); NSError *error; NSString *subString = [[songVar.songURL componentsSeparatedByString:@"id="] lastObject]; NSString *savedPath = [self.documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"audio%@.m4a",subString]];//save file name of converted 8kb song NSString *subStringPath = [NSString stringWithFormat:@"audio%@.m4a",subString]; if ([self.fileManager fileExistsAtPath:savedPath] == YES) [self.fileManager removeItemAtPath:savedPath error:&error]; NSLog(@"cacheDir %@",savedPath); //export low bitrate song to cache if ([self exportAudio:[AVAsset assetWithURL:songU] toFilePath:savedPath]) // HERE IS THE PROBLEM, this return true even the writing is not completed cos when i upload to my web server, it will say song file corrupted { // [self performSelector:@selector(sendSongForUpload:) withObject:subStringPath afterDelay:1]; [self sendRequest:2 andPath:subStringPath andSongDBItem:songVar]; } else { NSLog(@"song too short, skipped"); [self.uploadWorkingAray removeObjectAtIndex:0]; [self.songNotFoundArray addObject:songVar]; [self startUploadSongAnalysis]; } } else //uploadWorkingAray empty { NSLog(@"save changes"); [[VPPCoreData shanetworkingInstance] saveAllChanges]; } } #pragma mark song exporter to doc folder - (BOOL)exportAudio:(AVAsset *)avAsset toFilePath:(NSString *)filePath { CMTime assetTime = [avAsset duration]; Float64 duration = CMTimeGetSeconds(assetTime); if (duration < 40.0) return NO; // if song too short return no // get the first audio track NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeAudio]; if ([tracks count] == 0) return NO; NSError *readerError = nil; AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:avAsset error:&readerError]; //AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:avAsset error:&readerError]; // both works the same ? AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:avAsset.tracks audioSettings: nil]; if (! [reader canAddOutput: readerOutput]) { NSLog (@"can't add reader output...!"); return NO; } else { [reader addOutput:readerOutput]; } // writer AVFileTypeCoreAudioFormat AVFileTypeAppleM4A NSError *writerError = nil; AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:filePath] fileType:AVFileTypeAppleM4A error:&writerError]; //NSLog(@"writer %@",writer); AudioChannelLayout channelLayout; memset(&channelLayout, 0, sizeof(AudioChannelLayout)); channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; // use different values to affect the downsampling/compression // NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys: // [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey, // [NSNumber numberWithFloat:16000.0], AVSampleRateKey, // [NSNumber numberWithInt:2], AVNumberOfChannelsKey, // [NSNumber numberWithInt:128000], AVEncoderBitRateKey, // [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey, // nil]; NSDictionary *outputSettings = @{AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVEncoderBitRateKey: @(8000), AVNumberOfChannelsKey: @(1), AVSampleRateKey: @(8000)}; AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:outputSettings]; //\Add inputs to Write NSParameterAssert(writerInput); NSAssert([writer canAddInput:writerInput], @"Cannot write to this type of audio input" ); if ([writer canAddInput:writerInput]) { [writer addInput:writerInput]; } else { NSLog (@"can't add asset writer input... die!"); return NO; } [writerInput setExpectsMediaDataInRealTime:NO]; [writer startWriting]; [writer startSessionAtSourceTime:kCMTimeZero]; [reader startReading]; __block UInt64 convertedByteCount = 0; __block BOOL returnValue; __block CMSampleBufferRef nextBuffer; dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL); [writerInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^{ // NSLog(@"Asset Writer ready : %d", writerInput.readyForMoreMediaData); while (writerInput.readyForMoreMediaData) { nextBuffer = [readerOutput copyNextSampleBuffer]; if (nextBuffer) { [writerInput appendSampleBuffer: nextBuffer]; convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer); //NSNumber *convertedByteCountNumber = [NSNumber numberWithLong:convertedByteCount]; //NSLog (@"writing"); CFRelease(nextBuffer); } else { [writerInput markAsFinished]; [writer finishWritingWithCompletionHandler:^{ if (AVAssetWriterStatusCompleted == writer.status) { NSLog(@"Writer completed"); returnValue = YES; //I NEED TO RETURN SOMETHING FROM HERE AFTER WRITING COMPLETED dispatch_async(mediaInputQueue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ // add this to the main queue as the last item in my serial queue // when I get to this point I know everything in my queue has been run NSDictionary *outputFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; NSLog (@"done. file size is %lld", [outputFileAttributes fileSize]); }); }); } else if (AVAssetWriterStatusFailed == writer.status) { [writer cancelWriting]; [reader cancelReading]; NSLog(@"Writer failed"); return; } else { NSLog(@"Export Session Status: %d", writer.status); } }]; break; } } }]; tracks = nil; writer = nil; writerInput = nil; reader = nil; readerOutput=nil; mediaInputQueue = nil; return returnValue; //return YES; } 

Su método exportAudio:toFilePath: es en realidad un método asíncrono y requiere algunas correcciones para convertirse en un método asíncrono adecuado.

En primer lugar, debe proporcionar un controller de finalización para indicar al sitio de llamada que la tarea subyacente ha finalizado:

 - (void)exportAudio:(AVAsset *)avAsset toFilePath:(NSString *)filePath completion:(completion_t)completionHandler; 

Tenga en count que el resultado del método se pasa a través del manejador de finalización, cuya firma podría ser la siguiente:

 typedef void (^completion_t)(id result); 

donde el resultado del parámetro es el resultado final del método. Siempre debe devolver un object NSError cuando algo va mal al configurar varios objects dentro del método, aunque el método podría devolver un resultado inmediato que indique un error.

A continuación, si echa un vistazo a la documentation, puede leer:

requestMediaDataWhenReadyOnQueue: usingBlock:

 - (void)requestMediaDataWhenReadyOnQueue:(dispatch_queue_t)queue usingBlock:(void (^)(void))block 

Discusión

El bloque debe agregar datos de medios a la input, o bien hasta que la propiedad readyForMoreMediaData de la input sea NO o hasta que no haya más datos de medios para suministrar (en ese punto puede elegir marcar la input como terminada usando markAsFinished). El bloque debería salir. Una vez que el bloque finaliza, si la input no se marcó como terminada, una vez que la input haya procesado los datos multimedia que ha recibido y esté list para más datos multimedia, invocará el bloque nuevamente para get más.

Ahora deberías estar muy seguro de cuándo tu tarea está realmente terminada. Usted determina esto dentro del bloque que se pasa al método requestMediaDataWhenReadyOnQueue:usingBlock:

Cuando la tarea finaliza, llama al completador handler completionHandler proporcionado en el método exportAudio:toFilePath:completion:

Por supuesto, necesita corregir su implementación, por ejemplo, teniendo el método finalizado con

  tracks = nil; writer = nil; writerInput = nil; reader = nil; readerOutput=nil; mediaInputQueue = nil; return returnValue; //return YES; } 

Ciertamente no tiene sentido. La limpieza y devolución de un resultado se realizarán cuando la tarea asíncrona haya finalizado. A less que se produzca un error durante la installation, debe determinar esto en el bloque pasado al método requestMediaDataWhenReadyOnQueue:usingBlock:

En cualquier caso, para señalar el resultado a la llamada del sitio de llamada, el manejador de finalización completará el Handler y pasará un object de resultado, por ejemplo, si sucedió a la URL donde se ha guardado, de lo contrario un object NSError .

Ahora, ya que nuestro método startUploadSongAnalysis llama a un método asynchronous, ¡este método inevitable también se vuelve asíncrono!

Si entendí su código original correctamente, lo está invocando recursivamente para procesar una cantidad de activos. Para implementar esto correctamente, necesita algunas correcciones que se muestran a continuación. La "construcción" resultante NO es un método recursivo , sino una invocación iterativa de un método asynchronous ("bucle asíncrono").

Puede o no proporcionar un controller de finalización, igual que el anterior. Depende de usted, pero lo recomendaría, no hará daño saber cuándo se procesaron todos los activos. Puede verse de la siguiente manera:

 -(void)startUploadSongAnalysisWithCompletion:(completion_t)completionHandler { [self updateProgressYForID3NForUpload:NO]; // *** check for break condition: *** if ([self.uploadWorkingAray count]>=1) { ... stuff //export low bitrate song to cache [self exportAudio:[AVAsset assetWithURL:songU] toFilePath:savedPath completion:^(id urlOrError) { if ([urlOrError isKindOfClass[NSError class]]) { // Error occurnetworking: NSLog(@"Error: %@", urlOrError); // There are two alternatives to proceed: // A) Ignore or remember the error and proceed with the next asset. // In this case, it would be best to have a result array // containing all the results. Then, invoke // startUploadSongAnalysisWithCompletion: in order to proceed // with the next asset. // // B) Stop with error. // Don't call startUploadSongAnalysisWithCompletion: but // instead invoke the completion handler passing it the error. // A: // possibly dispatch to a sync queue or the main thread! [self.uploadWorkingAray removeObjectAtIndex:0]; [self.songNotFoundArray addObject:songVar]; // *** next song: *** [self startUploadSongAnalysisWithCompletion:completionHandler]; } else { // Success: // *** next song: *** NSURL* url = urlOrError; [self startUploadSongAnalysisWithCompletion:completionHandler]; } }]; } else //uploadWorkingAray empty { NSLog(@"save changes"); [[VPPCoreData shanetworkingInstance] saveAllChanges]; // *** signal completion *** if (completionHandler) { completionHandler(@"OK"); } } } 

No estoy seguro, pero no puedes enviar una llamada a un método como el siguiente

 dispatch_async(mediaInputQueue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ // add this to the main queue as the last item in my serial queue // when I get to this point I know everything in my queue has been run NSDictionary *outputFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; NSLog (@"done. file size is %lld", [outputFileAttributes fileSize]); //calling the following method after completing the queue [self printMe]; }); }); -(void)printMe{ NSLog(@"queue complete..."); //Do the next job, may be the following task !!! if ([self exportAudio:[AVAsset assetWithURL:songU] toFilePath:savedPath]) // HERE IS THE PROBLEM, this return true even the writing is not completed cos when i upload to my web server, it will say song file corrupted { // [self performSelector:@selector(sendSongForUpload:) withObject:subStringPath afterDelay:1]; [self sendRequest:2 andPath:subStringPath andSongDBItem:songVar]; } else { NSLog(@"song too short, skipped"); [self.uploadWorkingAray removeObjectAtIndex:0]; [self.songNotFoundArray addObject:songVar]; [self startUploadSongAnalysis]; } }