Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVAssetWriter ignores transform value

I'm using existing AVCaptureStillImageOutput bound to AVCaptureSession to get still images. Then I need to write them to AVAssetWriter and finally get video file filled with frames at 1-second interval.

Everything works fine except output video dimensions in portrait mode. When device in landscape mode - everything is fine, since captureStillImageAsynchronouslyFromConnection produce CMSampleBuffer with dimensions 1920x1080 (for instance), but when device is in portrait mode, it still produce rotated CMSampleBuffer with same dimensions (1920x1080). I can rotate final output video with .transform property of AVAssetWriterInput and it works fine, but final video have wrong dimensions (1920x1080), but should be 1080x1920.

I've figured out that problem is in captureStillImageAsynchronouslyFromConnection's CMSampleBuffer, which always have landscape dimensions. Then AVAssetWriter's input ignores configured width and height and use CMSampleBuffer's dimensions.

Is anybody know how to fix that?

ATTENTION: I know that it's possible to rotate captured buffer with vImage or Core Graphics functions, but I would like to avoid this method because of performance considerations. My problem looks like configuration issue or bug in iOS...

- (void) setupLongLoopWriter
{
self.currentFragment.filename2 = [VideoCapture getFilenameForNewFragment];
NSString *path = [NSString stringWithFormat:@"%@/%@", [GMConfig getVideoCapturesPath], self.currentFragment.filename2];

CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(self.currentCamera.activeFormat.formatDescription);

CGSize videoWriterFrameSize = CGSizeMake(dimensions.width, dimensions.height);

float rotationAngle = 0.0;

switch ((UIDeviceOrientation)[self.currentFragment.orientation unsignedIntValue])
{
    case UIDeviceOrientationUnknown:
    case UIDeviceOrientationPortrait:
    case UIDeviceOrientationFaceUp:
    case UIDeviceOrientationFaceDown:
        rotationAngle = DEGREES_TO_RADIANS(90);
        videoWriterFrameSize = CGSizeMake(dimensions.height, dimensions.width);
        break;

    case UIDeviceOrientationPortraitUpsideDown:
        rotationAngle = DEGREES_TO_RADIANS(-90.0);
        videoWriterFrameSize = CGSizeMake(dimensions.height, dimensions.width);
        break;

    case UIDeviceOrientationLandscapeLeft:
        rotationAngle = 0.0;
        break;

    case UIDeviceOrientationLandscapeRight:
        rotationAngle = DEGREES_TO_RADIANS(180.0);
        break;
}

    //  NSLog(@"%.0fx%.0f", videoWriterFrameSize.width, videoWriterFrameSize.height);

NSError *error = nil;

self.currentFragment.longLoopWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path]
                                                       fileType:AVFileTypeQuickTimeMovie
                                                          error:&error];
NSParameterAssert(self.currentFragment.longLoopWriter);

NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                               AVVideoCodecH264, AVVideoCodecKey,
                               AVVideoScalingModeResizeAspect, AVVideoScalingModeKey,
                               [NSNumber numberWithInt:videoWriterFrameSize.width], AVVideoWidthKey,
                               [NSNumber numberWithInt:videoWriterFrameSize.height], AVVideoHeightKey,
                               nil];

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

if (rotationAngle != 0.0)
    writerInput.transform = CGAffineTransformMakeRotation (rotationAngle);


NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                                       [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];

self.currentFragment.longLoopWriterAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];

NSParameterAssert(writerInput);
NSParameterAssert([self.currentFragment.longLoopWriter canAddInput:writerInput]);


[self.currentFragment.longLoopWriter addInput:writerInput];

if( [self.currentFragment.longLoopWriter startWriting] == NO )
    NSLog(@"Failed to start long loop writing!");

[self.currentFragment.longLoopWriter startSessionAtSourceTime:kCMTimeZero];
}

- (void) captureLongLoopFrame
{
if ([GMConfig sharedConfig].longLoopFrameDuration == 0.0) {
    [self.longLoopCaptureTimer invalidate];
    self.longLoopCaptureTimer = nil;

    return;
}

if (self.captureSession.isRunning == NO || self.currentFragment.longLoopWriterAdaptor == nil)
    return;

[self.shutterOutput captureStillImageAsynchronouslyFromConnection:[self.shutterOutput.connections objectAtIndex:0]
                                                completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
    if (imageDataSampleBuffer != NULL && error == nil) {
        /*
        CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(CMSampleBufferGetFormatDescription(imageDataSampleBuffer));
        CGSize videoWriterFrameSize = CGSizeMake(dimensions.width, dimensions.height);

        NSLog(@"Image buffer size: %.0fx%.0f", videoWriterFrameSize.width, videoWriterFrameSize.height);
        */

        double offset = [[NSDate date] timeIntervalSinceDate:self.currentFragment.start];
        CMTime presentationTime = CMTimeMake(offset*1000, 1000);

        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(imageDataSampleBuffer);




        if (self.currentFragment.longLoopWriterAdaptor.assetWriterInput.readyForMoreMediaData == YES) {
            if( [self.currentFragment.longLoopWriterAdaptor appendPixelBuffer:imageBuffer withPresentationTime:presentationTime] == NO ) {
                NSLog(@"Error adding frame to long loop!");
            }
        }

        NSLog(@"Long loop updated at %0.1f", CMTimeGetSeconds(presentationTime));
    }
}];
}
like image 367
AlexeyVMP Avatar asked Apr 06 '14 10:04

AlexeyVMP


1 Answers

I record in portrait mode only and i was able to avoid this issue by getting rotated pixel data directly from the camera using setVideoOrientation on the AVCaptureConnection to pass to AVAssetWriter (see https://developer.apple.com/library/ios/qa/qa1744/_index.html)

for (AVCaptureConnection *connection in [videoOutput connections] ) {
            for ( AVCaptureInputPort *port in [connection inputPorts] ) {
                if ( [[port mediaType] isEqual:AVMediaTypeVideo] ) {

                    [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];

                }
            }
}
like image 165
Ed Filowat Avatar answered Oct 16 '22 13:10

Ed Filowat