¿Cómo utilizar AVAssetReader y AVAssetWriter para varias pistas (audio y video) simultáneamente?

Sé cómo usar AVAssetReader y AVAssetWriter, y los he utilizado con éxito para tomar una pista de video de una película y transcodificarla en otra. Sin embargo, me gustaría hacer esto con audio también. ¿Tengo que crear y AVAssetExportSession después de que haya terminado con la transencoding inicial, o hay alguna forma de cambiar entre pistas mientras estoy en medio de una session de escritura? Odiaría tener que lidiar con la sobrecarga de una AVAssetExportSession.

Pregunto porque, utilizando el método de estilo de extracción (mientras que ([assetWriterInput isReadyForMoreMediaData]) {…} – asume solo una pista. ¿Cómo podría usarse para más de una pista, es decir, tanto una pista de audio como una de video?

AVAssetWriter automáticamente intercala requestes en su AVAssetWriterInput s asociado para integrar diferentes pistas en el file de salida. Simplemente agregue AVAssetWriterInput para cada una de las pistas que tenga, y luego llame a requestMediaDataWhenReadyOnQueue:usingBlock: en cada uno de sus AVAssetWriterInput s.

Aquí hay un método que tengo que llama a requestMediaDataWhenReadyOnQueue:usingBlock: Llamo a este método a partir de un ciclo sobre el número de pares de salida / input que tengo. (Un método por separado es bueno para la legibilidad del código y también porque, a diferencia de un ciclo, cada llamada configura una ttwig de stack separada para el bloque).

Solo necesita una dispatch_queue_t y puede volver a usarla para todas las pistas. Tenga en count que definitivamente no debe llamar a dispatch_async desde su bloque, ya que requestMediaDataWhenReadyOnQueue:usingBlock: espera que el bloque bloquee hasta que haya completado la cantidad de datos que tomará AVAssetWriterInput . No quieres volver antes.

 - (void)requestMediaDataForTrack:(int)i { AVAssetReaderOutput *output = [[_reader outputs] objectAtIndex:i]; AVAssetWriterInput *input = [[_writer inputs] objectAtIndex:i]; [input requestMediaDataWhenReadyOnQueue:_processingQueue usingBlock: ^{ [self retain]; while ([input isReadyForMoreMediaData]) { CMSampleBufferRef sampleBuffer; if ([_reader status] == AVAssetReaderStatusReading && (sampleBuffer = [output copyNextSampleBuffer])) { BOOL result = [input appendSampleBuffer:sampleBuffer]; CFRelease(sampleBuffer); if (!result) { [_reader cancelReading]; break; } } else { [input markAsFinished]; switch ([_reader status]) { case AVAssetReaderStatusReading: // the reader has more for other tracks, even if this one is done break; case AVAssetReaderStatusCompleted: // your method for when the conversion is done // should call finishWriting on the writer [self readingCompleted]; break; case AVAssetReaderStatusCancelled: [_writer cancelWriting]; [_delegate converterDidCancel:self]; break; case AVAssetReaderStatusFailed: [_writer cancelWriting]; break; } break; } } } ]; } 

¿Ha intentado usar dos AVAssetWriterInputs y ha empujado las muestras a través de una queue de trabajo? Aquí hay un boceto aproximado.

 processing_queue = dispatch_queue_create("com.mydomain.gcdqueue.mediaprocessor", NULL); [videoAVAssetWriterInput requestMediaDataWhenReadyOnQueue:myInputSerialQueue usingBlock:^{ dispatch_asyc(processing_queue, ^{process video}); }]; [audioAVAssetWriterInput requestMediaDataWhenReadyOnQueue:myInputSerialQueue usingBlock:^{ dispatch_asyc(processing_queue, ^{process audio}); }]; 

¡Puedes usar grupos de envío!

Mira el ejemplo de AVReaderWriter para MacOSX …

Estoy citando directamente de la muestra RWDocument.m:

 - (BOOL)startReadingAndWritingReturningError:(NSError **)outError { BOOL success = YES; NSError *localError = nil; // Instruct the asset reader and asset writer to get ready to do work success = [assetReader startReading]; if (!success) localError = [assetReader error]; if (success) { success = [assetWriter startWriting]; if (!success) localError = [assetWriter error]; } if (success) { dispatch_group_t dispatchGroup = dispatch_group_create(); // Start a sample-writing session [assetWriter startSessionAtSourceTime:[self timeRange].start]; // Start reading and writing samples if (audioSampleBufferChannel) { // Only set audio delegate for audio-only assets, else let the video channel drive progress id <RWSampleBufferChannelDelegate> delegate = nil; if (!videoSampleBufferChannel) delegate = self; dispatch_group_enter(dispatchGroup); [audioSampleBufferChannel startWithDelegate:delegate completionHandler:^{ dispatch_group_leave(dispatchGroup); }]; } if (videoSampleBufferChannel) { dispatch_group_enter(dispatchGroup); [videoSampleBufferChannel startWithDelegate:self completionHandler:^{ dispatch_group_leave(dispatchGroup); }]; } // Set up a callback for when the sample writing is finished dispatch_group_notify(dispatchGroup, serializationQueue, ^{ BOOL finalSuccess = YES; NSError *finalError = nil; if (cancelled) { [assetReader cancelReading]; [assetWriter cancelWriting]; } else { if ([assetReader status] == AVAssetReaderStatusFailed) { finalSuccess = NO; finalError = [assetReader error]; } if (finalSuccess) { finalSuccess = [assetWriter finishWriting]; if (!finalSuccess) finalError = [assetWriter error]; } } [self readingAndWritingDidFinishSuccessfully:finalSuccess withError:finalError]; }); dispatch_release(dispatchGroup); } if (outError) *outError = localError; return success; }