AVFoundation de iOS: Configuración de la orientación del video

He estado luchando con varias dimensiones para el problema de controlar la orientación del video durante y después de la captura en un dispositivo con iOS. Gracias a las respuestas anteriores y la documentación de Apple, he podido descifrarlo. Sin embargo, ahora que quiero enviar algunos videos a un sitio web, me encuentro con problemas particulares. Describí este problema en particular en esta pregunta , y la solución propuesta requiere que se establezcan opciones de orientación durante la encoding del video.

Eso puede ser, pero no tengo ni idea de cómo hacerlo. La documentación sobre la configuración de la orientación se refiere a la configuración correcta para su visualización en el dispositivo, y he implementado los consejos que se encuentran aquí. Sin embargo, este consejo no aborda la configuración adecuada de la orientación para software que no es de Apple, como VLC o el navegador Chrome.

¿Alguien puede proporcionar información sobre cómo establecer la orientación correctamente en el dispositivo para que se muestre correctamente para todo el software de visualización?

Finalmente, en base a las respuestas de @Aaron Vegh y @Prince, resolví mi resolución: // Conversión de video

 +(void)convertMOVToMp4:(NSString *)movFilePath completion:(void (^)(NSString *mp4FilePath))block{ AVURLAsset * videoAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:movFilePath] options:nil]; AVAssetTrack *sourceAudioTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; AVMutableComposition* composition = [AVMutableComposition composition]; AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceAudioTrack atTime:kCMTimeZero error:nil]; AVAssetExportSession * assetExport = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality]; NSString *exportPath = [movFilePath stringByReplacingOccurrencesOfString:@".MOV" withString:@".mp4"]; NSURL * exportUrl = [NSURL fileURLWithPath:exportPath]; assetExport.outputFileType = AVFileTypeMPEG4; assetExport.outputURL = exportUrl; assetExport.shouldOptimizeForNetworkUse = YES; assetExport.videoComposition = [self getVideoComposition:videoAsset composition:composition]; [assetExport exportAsynchronouslyWithCompletionHandler: ^(void ) { switch (assetExport.status) { case AVAssetExportSessionStatusCompleted: // export complete if (block) { block(exportPath); } break; case AVAssetExportSessionStatusFailed: block(nil); break; case AVAssetExportSessionStatusCancelled: block(nil); break; } }]; } 

// obtener la orientación actual

  +(AVMutableVideoComposition *) getVideoComposition:(AVAsset *)asset composition:( AVMutableComposition*)composition{ BOOL isPortrait_ = [self isVideoPortrait:asset]; AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil]; AVMutableVideoCompositionLayerInstruction *layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack]; CGAffineTransform transform = videoTrack.preferredTransform; [layerInst setTransform:transform atTime:kCMTimeZero]; AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration); inst.layerInstructions = [NSArray arrayWithObject:layerInst]; AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; videoComposition.instructions = [NSArray arrayWithObject:inst]; CGSize videoSize = videoTrack.naturalSize; if(isPortrait_) { NSLog(@"video is portrait "); videoSize = CGSizeMake(videoSize.height, videoSize.width); } videoComposition.renderSize = videoSize; videoComposition.frameDuration = CMTimeMake(1,30); videoComposition.renderScale = 1.0; return videoComposition; } 

// obtener video

 +(BOOL) isVideoPortrait:(AVAsset *)asset{ BOOL isPortrait = FALSE; NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; if([tracks count] > 0) { AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; CGAffineTransform t = videoTrack.preferredTransform; // Portrait if(ta == 0 && tb == 1.0 && tc == -1.0 && td == 0) { isPortrait = YES; } // PortraitUpsideDown if(ta == 0 && tb == -1.0 && tc == 1.0 && td == 0) { isPortrait = YES; } // LandscapeRight if(ta == 1.0 && tb == 0 && tc == 0 && td == 1.0) { isPortrait = FALSE; } // LandscapeLeft if(ta == -1.0 && tb == 0 && tc == 0 && td == -1.0) { isPortrait = FALSE; } } return isPortrait; 

}

En la documentación de Apple aquí dice:

Los clientes ahora pueden recibir CVPixelBuffers girados físicamente en su AVCaptureVideoDataOutput -captureOutput: didOutputSampleBuffer: fromConnection: delegan la callback. En versiones anteriores de iOS, la cámara frontal siempre entregaba búferes en AVCaptureVideoOrientationLandscapeLeft y la cámara trasera siempre entregaba búferes en AVCaptureVideoOrientationLandscapeRight. Las 4 AVCaptureVideoOrientations son compatibles y la rotación es acelerada por hardware. Para solicitar la rotación del búfer, un cliente llama a -setVideoOrientation: en el video AVCaptureVideoDataOutput AVCaptureConnection de AVCaptureVideoDataOutput. Tenga en cuenta que los buffers que giran físicamente vienen con un costo de rendimiento, por lo que solo solicite la rotación si es necesario. Si, por ejemplo, desea que el video girado se grabe en un archivo de película QuickTime utilizando AVAssetWriter, es preferible establecer la propiedad -transform en el AVAssetWriterInput en lugar de rotar físicamente los buffers en AVCaptureVideoDataOutput.

Entonces, la solución publicada por Aaron Vegh que usa una sesión AVAssetExportSession funciona, pero no es necesaria. Al igual que los documentos de Apple, si desea tener la orientación configurada correctamente para que se reproduzca en reproductores Quicktime que no sean de Apple como VLC o en la web con Chrome, debe establecer la orientación de video en AVCaptureConnection para AVCaptureVideoDataOutput. Si intentas configurarlo para AVAssetWriterInput obtendrás una orientación incorrecta para jugadores como VLC y Chrome.

Aquí está mi código donde lo configuré durante la configuración de la sesión de captura:

 // DECLARED AS PROPERTIES ABOVE @property (strong,nonatomic) AVCaptureDeviceInput *audioIn; @property (strong,nonatomic) AVCaptureAudioDataOutput *audioOut; @property (strong,nonatomic) AVCaptureDeviceInput *videoIn; @property (strong,nonatomic) AVCaptureVideoDataOutput *videoOut; @property (strong,nonatomic) AVCaptureConnection *audioConnection; @property (strong,nonatomic) AVCaptureConnection *videoConnection; ------------------------------------------------------------------ ------------------------------------------------------------------ -(void)setupCaptureSession{ // Setup Session self.session = [[AVCaptureSession alloc]init]; [self.session setSessionPreset:AVCaptureSessionPreset640x480]; // Create Audio connection ---------------------------------------- self.audioIn = [[AVCaptureDeviceInput alloc]initWithDevice:[self getAudioDevice] error:nil]; if ([self.session canAddInput:self.audioIn]) { [self.session addInput:self.audioIn]; } self.audioOut = [[AVCaptureAudioDataOutput alloc]init]; dispatch_queue_t audioCaptureQueue = dispatch_queue_create("Audio Capture Queue", DISPATCH_QUEUE_SERIAL); [self.audioOut setSampleBufferDelegate:self queue:audioCaptureQueue]; if ([self.session canAddOutput:self.audioOut]) { [self.session addOutput:self.audioOut]; } self.audioConnection = [self.audioOut connectionWithMediaType:AVMediaTypeAudio]; // Create Video connection ---------------------------------------- self.videoIn = [[AVCaptureDeviceInput alloc]initWithDevice:[self videoDeviceWithPosition:AVCaptureDevicePositionBack] error:nil]; if ([self.session canAddInput:self.videoIn]) { [self.session addInput:self.videoIn]; } self.videoOut = [[AVCaptureVideoDataOutput alloc]init]; [self.videoOut setAlwaysDiscardsLateVideoFrames:NO]; [self.videoOut setVideoSettings:nil]; dispatch_queue_t videoCaptureQueue = dispatch_queue_create("Video Capture Queue", DISPATCH_QUEUE_SERIAL); [self.videoOut setSampleBufferDelegate:self queue:videoCaptureQueue]; if ([self.session canAddOutput:self.videoOut]) { [self.session addOutput:self.videoOut]; } self.videoConnection = [self.videoOut connectionWithMediaType:AVMediaTypeVideo]; // SET THE ORIENTATION HERE ------------------------------------------------- [self.videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait]; // -------------------------------------------------------------------------- // Create Preview Layer ------------------------------------------- AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session]; CGRect bounds = self.videoView.bounds; previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; previewLayer.bounds = bounds; previewLayer.position=CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); [self.videoView.layer addSublayer:previewLayer]; // Start session [self.session startRunning]; 

}

En caso de que alguien más esté buscando esta respuesta también, este es el método que preparé (modificado un poco para simplificar):

 - (void)encodeVideoOrientation:(NSURL *)anOutputFileURL { CGAffineTransform rotationTransform; CGAffineTransform rotateTranslate; CGSize renderSize; switch (self.recordingOrientation) { // set these 3 values based on orientation } AVURLAsset * videoAsset = [[AVURLAsset alloc]initWithURL:anOutputFileURL options:nil]; AVAssetTrack *sourceVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *sourceAudioTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; AVMutableComposition* composition = [AVMutableComposition composition]; AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceVideoTrack atTime:kCMTimeZero error:nil]; [compositionVideoTrack setPreferredTransform:sourceVideoTrack.preferredTransform]; AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceAudioTrack atTime:kCMTimeZero error:nil]; AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack]; [layerInstruction setTransform:rotateTranslate atTime:kCMTimeZero]; AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; videoComposition.frameDuration = CMTimeMake(1,30); videoComposition.renderScale = 1.0; videoComposition.renderSize = renderSize; instruction.layerInstructions = [NSArray arrayWithObject: layerInstruction]; instruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration); videoComposition.instructions = [NSArray arrayWithObject: instruction]; AVAssetExportSession * assetExport = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality]; NSString* videoName = @"export.mov"; NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:videoName]; NSURL * exportUrl = [NSURL fileURLWithPath:exportPath]; if ([[NSFileManager defaultManager] fileExistsAtPath:exportPath]) { [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil]; } assetExport.outputFileType = AVFileTypeMPEG4; assetExport.outputURL = exportUrl; assetExport.shouldOptimizeForNetworkUse = YES; assetExport.videoComposition = videoComposition; [assetExport exportAsynchronouslyWithCompletionHandler: ^(void ) { switch (assetExport.status) { case AVAssetExportSessionStatusCompleted: // export complete NSLog(@"Export Complete"); break; case AVAssetExportSessionStatusFailed: NSLog(@"Export Failed"); NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]); // export error (see exportSession.error) break; case AVAssetExportSessionStatusCancelled: NSLog(@"Export Failed"); NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]); // export cancelled break; } }]; } 

Por desgracia, este material está poco documentado, pero al unir ejemplos de otras preguntas de SO y leer los archivos de encabezado, pude hacer que esto funcionara. Espero que esto ayude a los demás!

Utilice estos method a continuación para establecer correct orientation correct acuerdo con video orientation asset video en AVMutableVideoComposition

 -(AVMutableVideoComposition *) getVideoComposition:(AVAsset *)asset { AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVMutableComposition *composition = [AVMutableComposition composition]; AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; CGSize videoSize = videoTrack.naturalSize; BOOL isPortrait_ = [self isVideoPortrait:asset]; if(isPortrait_) { NSLog(@"video is portrait "); videoSize = CGSizeMake(videoSize.height, videoSize.width); } composition.naturalSize = videoSize; videoComposition.renderSize = videoSize; // videoComposition.renderSize = videoTrack.naturalSize; // videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600); AVMutableCompositionTrack *compositionVideoTrack; compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil]; AVMutableVideoCompositionLayerInstruction *layerInst; layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack]; [layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero]; AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration); inst.layerInstructions = [NSArray arrayWithObject:layerInst]; videoComposition.instructions = [NSArray arrayWithObject:inst]; return videoComposition; } -(BOOL) isVideoPortrait:(AVAsset *)asset { BOOL isPortrait = FALSE; NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; if([tracks count] > 0) { AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; CGAffineTransform t = videoTrack.preferredTransform; // Portrait if(ta == 0 && tb == 1.0 && tc == -1.0 && td == 0) { isPortrait = YES; } // PortraitUpsideDown if(ta == 0 && tb == -1.0 && tc == 1.0 && td == 0) { isPortrait = YES; } // LandscapeRight if(ta == 1.0 && tb == 0 && tc == 0 && td == 1.0) { isPortrait = FALSE; } // LandscapeLeft if(ta == -1.0 && tb == 0 && tc == 0 && td == -1.0) { isPortrait = FALSE; } } return isPortrait; } 

Desde iOS 5, puede solicitar CVPixelBuffers girados utilizando AVCaptureVideoDataOutput documentado aquí . Esto le da la orientación correcta sin tener que volver a procesar el video con AVAssetExportSession.