2014-02-18 8 views
5

Ich verwende AVCaptureSession, um Videos von einer Gerätekamera aufzunehmen und dann AVAssetWriterInput und AVAssetTrack zu verwenden, um das Video zu komprimieren/zu skalieren, bevor es auf einen Server hochgeladen wird. Die endgültigen Videos werden im Web über ein html5-Videoelement angezeigt.AVFoundation - Warum kann ich die Videoausrichtung nicht richtig einstellen?

Ich habe mehrere Probleme, die versuchen, die richtige Ausrichtung des Videos zu erhalten. Meine App unterstützt nur die Ausrichtung im Querformat und alle aufgenommenen Videos sollten im Querformat ausgerichtet sein. Jedoch möchte ich dem Benutzer erlauben, sein Gerät in beiden Querformat-Richtungen zu halten (d. H. Home-Taste auf der linken oder der rechten Seite).

Ich bin die Lage, die Video-Vorschau zeigt in der richtigen Ausrichtung mit der folgenden Codezeile

_previewLayer.connection.videoOrientation = UIDevice.currentDevice.orientation; 

Die Probleme beginnen zu machen, wenn das Video über AVAssetWriterInput und Freunde zu verarbeiten. Das Ergebnis scheint nicht für den linken oder rechten Landschaftsmodus zu sein, in dem das Video aufgenommen wurde. IOW, manchmal erscheint das Video auf dem Kopf stehend. Nach einigen googeln fand ich viele Leute darauf hindeutet, dass die folgende Codezeile dieses Problem lösen würde

writerInput.transform = videoTrack.preferredTransform; 

... aber das scheint nicht zu funktionieren. Nach einem wenig Debuggen fand ich, dass videoTrack.preferredTransform immer der gleiche Wert ist, unabhängig von der Orientierung wurde das Video in eingefangen.

versuchte Tracking I manuell welche Orientierung wurde das Video aufgenommen und in den zu writerInput.transformCGAffineTransformMakeRotation(M_PI) nach Bedarf einstellen. Was hat das Problem gelöst !!!

... sorta

Als ich gesehen die Ergebnisse auf dem Gerät diese Lösung wie erwartet funktioniert. Die Videos waren während der Aufnahme unabhängig von linker und rechter Ausrichtung rechts oben. Leider, als ich die gleichen Videos in einem anderen Browser (Chrome auf einem Mac-Buch) angesehen habe, waren sie alle auf dem Kopf stehend!?!?!?

Was mache ich falsch?

EDIT

Hier ist ein Code, falls hilfreich es ist ...

-(void)compressFile:(NSURL*)inUrl; 
{     
    NSString* fileName = [@"compressed." stringByAppendingString:inUrl.lastPathComponent]; 
    NSError* error; 
    NSURL* outUrl = [PlatformHelper getFilePath:fileName error:&error]; 

    NSDictionary* compressionSettings = @{ AVVideoProfileLevelKey: AVVideoProfileLevelH264Main31, 
              AVVideoAverageBitRateKey: [NSNumber numberWithInt:2500000], 
              AVVideoMaxKeyFrameIntervalKey: [NSNumber numberWithInt: 30] }; 

    NSDictionary* videoSettings = @{ AVVideoCodecKey: AVVideoCodecH264, 
            AVVideoWidthKey: [NSNumber numberWithInt:1280], 
            AVVideoHeightKey: [NSNumber numberWithInt:720], 
            AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill, 
            AVVideoCompressionPropertiesKey: compressionSettings }; 

    NSDictionary* videoOptions = @{ (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] }; 


    AVAssetWriterInput* writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings]; 
    writerInput.expectsMediaDataInRealTime = YES; 

    AVAssetWriter* assetWriter = [AVAssetWriter assetWriterWithURL:outUrl fileType:AVFileTypeMPEG4 error:&error]; 
    assetWriter.shouldOptimizeForNetworkUse = YES; 

    [assetWriter addInput:writerInput]; 

    AVURLAsset* asset = [AVURLAsset URLAssetWithURL:inUrl options:nil]; 
    AVAssetTrack* videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; 

    // !!! this line does not work as expected and causes all sorts of issues (videos display sideways in some cases) !!! 
    //writerInput.transform = videoTrack.preferredTransform; 

    AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:videoOptions]; 
    AVAssetReader* assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error]; 

    [assetReader addOutput:readerOutput]; 

    [assetWriter startWriting]; 
    [assetWriter startSessionAtSourceTime:kCMTimeZero]; 
    [assetReader startReading]; 

    [writerInput requestMediaDataWhenReadyOnQueue:_processingQueue usingBlock: 
    ^{ 
     /* snip */ 
    }]; 
} 

Antwort

7

Das Problem ist, dass die Modifizierung writerInput.transform Eigenschaft nur einen Tag in der Videodatei Metadaten hinzugefügt, die das Video anweist Player, um die Datei während der Wiedergabe zu drehen. Deshalb werden die Videos auf Ihrem Gerät in der richtigen Ausrichtung wiedergegeben (ich vermute, dass sie auch in einem Quicktime-Player korrekt wiedergegeben werden).

Die von der Kamera erfassten Pixelpuffer sind immer noch in der Ausrichtung angeordnet, in der sie erfasst wurden. Viele Videoplayer überprüfen nicht das bevorzugte Metadaten-Tag für die Ausrichtung und spielen die Datei nur in der nativen Pixelausrichtung ab.

Wenn Sie der Benutzer möchte in der Lage sein Video aufnehmen in das Telefon gehalten wird entweder Landscape-Modus, müssen Sie dies auf AVCaptureSession Niveau vor der Kompression korrigieren, indem ein auf dem CVPixelBuffer jedes Video-Frame-Transformation.Das Apple-Q & A deckt (Blick auf die AVCaptureVideoOutput Dokumentation als auch): https://developer.apple.com/library/ios/qa/qa1744/_index.html

Untersuchung der Verbindung über die richtige Art und Weise ist, Ihr Problem zu lösen. Eine Alternative schnell n 'schmutzig Weg, um das gleiche Problem zu lösen wäre, die Aufnahme UI Ihrer App in nur eine Querformatausrichtung zu sperren und dann alle Ihre Videos serverseitig mit ffmpeg drehen.

+0

Vielen Dank. – herbrandson

0

Falls es für jemanden hilfreich ist, hier ist der Code, mit dem ich endete. Am Ende musste ich die Arbeit an dem Video machen, als es aufgenommen wurde, anstatt als Nachbearbeitungsschritt. Dies ist eine Hilfsklasse, die das Capture verwaltet.

Schnittstelle

#import <Foundation/Foundation.h> 
#import <AVFoundation/AVFoundation.h> 

@interface VideoCaptureManager : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> 
{ 
    AVCaptureSession* _captureSession; 
    AVCaptureVideoPreviewLayer* _previewLayer; 
    AVCaptureVideoDataOutput* _videoOut; 
    AVCaptureDevice* _videoDevice; 
    AVCaptureDeviceInput* _videoIn; 
    dispatch_queue_t _videoProcessingQueue; 

    AVAssetWriter* _assetWriter; 
    AVAssetWriterInput* _writerInput; 

    BOOL _isCapturing; 
    NSString* _gameId; 
    NSString* _authToken; 
} 

-(void)setSettings:(NSString*)gameId authToken:(NSString*)authToken; 
-(void)setOrientation:(AVCaptureVideoOrientation)orientation; 
-(AVCaptureVideoPreviewLayer*)getPreviewLayer; 
-(void)startPreview; 
-(void)stopPreview; 
-(void)startCapture; 
-(void)stopCapture; 

@end 

Implementation (w/ein wenig Bearbeitung und ein paar kleine Todo)

@implementation VideoCaptureManager 

-(id)init; 
{ 
    self = [super init]; 
    if (self) { 
     NSError* error; 

     _videoProcessingQueue = dispatch_queue_create("VideoQueue", DISPATCH_QUEUE_SERIAL); 
     _captureSession = [AVCaptureSession new]; 

     _videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 

     _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession]; 
     [_previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; 

     _videoOut = [AVCaptureVideoDataOutput new]; 
     _videoOut.videoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] }; 
     _videoOut.alwaysDiscardsLateVideoFrames = YES; 

     _videoIn = [AVCaptureDeviceInput deviceInputWithDevice:_videoDevice error:&error]; 
     // handle errors here 

     [_captureSession addInput:_videoIn]; 
     [_captureSession addOutput:_videoOut]; 
    } 

    return self; 
} 

-(void)setOrientation:(AVCaptureVideoOrientation)orientation; 
{ 
    _previewLayer.connection.videoOrientation = orientation; 
    for (AVCaptureConnection* item in _videoOut.connections) { 
     item.videoOrientation = orientation; 
    } 
} 

-(AVCaptureVideoPreviewLayer*)getPreviewLayer; 
{ 
    return _previewLayer; 
} 

-(void)startPreview; 
{ 
    [_captureSession startRunning]; 
} 

-(void)stopPreview; 
{ 
    [_captureSession stopRunning]; 
} 

-(void)startCapture; 
{ 
    if (_isCapturing) return; 

    NSURL* url = put code here to create your output url 

    NSDictionary* compressionSettings = @{ AVVideoProfileLevelKey: AVVideoProfileLevelH264Main31, 
              AVVideoAverageBitRateKey: [NSNumber numberWithInt:2500000], 
              AVVideoMaxKeyFrameIntervalKey: [NSNumber numberWithInt: 1], 
             }; 

    NSDictionary* videoSettings = @{ AVVideoCodecKey: AVVideoCodecH264, 
            AVVideoWidthKey: [NSNumber numberWithInt:1280], 
            AVVideoHeightKey: [NSNumber numberWithInt:720], 
            AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill, 
            AVVideoCompressionPropertiesKey: compressionSettings 
            }; 

    _writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings]; 
    _writerInput.expectsMediaDataInRealTime = YES; 

    NSError* error; 
    _assetWriter = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:&error]; 
    // handle errors 

    _assetWriter.shouldOptimizeForNetworkUse = YES; 
    [_assetWriter addInput:_writerInput]; 
    [_videoOut setSampleBufferDelegate:self queue:_videoProcessingQueue]; 

    _isCapturing = YES; 
} 

-(void)stopCapture; 
{ 
    if (!_isCapturing) return; 

    [_videoOut setSampleBufferDelegate:nil queue:nil]; // TODO: seems like there could be a race condition between this line and the next (could end up trying to write a buffer after calling writingFinished 

    dispatch_async(_videoProcessingQueue, ^{ 
     [_assetWriter finishWritingWithCompletionHandler:^{ 
      [self writingFinished]; 
     }]; 
    }); 
} 

-(void)writingFinished; 
{ 
    // TODO: need to check _assetWriter.status to make sure everything completed successfully 
    // do whatever post processing you need here 
} 


-(void)captureOutput:(AVCaptureOutput*)captureOutput didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection; 
{ 
    NSLog(@"Video frame was dropped."); 
} 

-(void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 
{ 
    if(_assetWriter.status != AVAssetWriterStatusWriting) { 
     CMTime lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 
     [_assetWriter startWriting]; // TODO: need to check the return value (a bool) 
     [_assetWriter startSessionAtSourceTime:lastSampleTime]; 
    } 

    if (!_writerInput.readyForMoreMediaData || ![_writerInput appendSampleBuffer:sampleBuffer]) { 
     NSLog(@"Failed to write video buffer to output."); 
    } 
} 

@end 
1

zum Komprimieren/Ändern der Größe der Video können wir AVAssetExportSession verwenden.

  • Wir können ein Video der Dauer 3,30 Minuten hochladen.
  • Wenn die Videodauer mehr als 3,30 Minuten beträgt, wird eine Speicherwarnung angezeigt.
  • Da wir hier keine Transformation für das Video verwenden, wird das Video so sein, wie es ist während der Aufnahme.
  • Unten finden Sie den Beispielcode zum Komprimieren des Videos.
  • können wir die Videogröße vor der Komprimierung und nach der Komprimierung überprüfen.

{

-(void)trimVideoWithURL:(NSURL *)inputURL{ 


NSString *path1 = [inputURL path]; 
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path1]; 
NSLog(@"size before compress video is %lu",(unsigned long)data.length); 

AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil]; 
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPreset640x480]; 

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *outputURL = paths[0]; 
NSFileManager *manager = [NSFileManager defaultManager]; 
[manager createDirectoryAtPath:outputURL withIntermediateDirectories:YES attributes:nil error:nil]; 
outputURL = [outputURL stringByAppendingPathComponent:@"output.mp4"]; 
fullPath = [NSURL URLWithString:outputURL]; 

// Remove Existing File 

[manager removeItemAtPath:outputURL error:nil]; 

exportSession.outputURL = [NSURL fileURLWithPath:outputURL]; 
exportSession.shouldOptimizeForNetworkUse = YES; 
exportSession.outputFileType = AVFileTypeQuickTimeMovie; 

CMTime start = CMTimeMakeWithSeconds(1.0, 600); 
CMTime duration = CMTimeMakeWithSeconds(1.0, 600); 
CMTimeRange range = CMTimeRangeMake(start, duration); 
exportSession.timeRange = range; 

[exportSession exportAsynchronouslyWithCompletionHandler:^(void) 
{ 
    switch (exportSession.status) { 

     case AVAssetExportSessionStatusCompleted:{ 

      NSString *path = [fullPath path]; 
      NSData *data = [[NSFileManager defaultManager] contentsAtPath:path]; 
      NSLog(@"size after compress video is %lu",(unsigned long)data.length); 
      NSLog(@"Export Complete %d %@", exportSession.status, exportSession.error); 
      /* 
       Do your neccessay stuff here after compression 
       */ 

     } 
      break; 
     case AVAssetExportSessionStatusFailed: 
      NSLog(@"Failed:%@",exportSession.error); 
      break; 
     case AVAssetExportSessionStatusCancelled: 
      NSLog(@"Canceled:%@",exportSession.error); 
      break; 
     default: 
      break; 
    } 
}];} 
Verwandte Themen