旋转视频而不旋转 AVCaptureConnection 并在 AVAssetWriter 会话中间

Posted

技术标签:

【中文标题】旋转视频而不旋转 AVCaptureConnection 并在 AVAssetWriter 会话中间【英文标题】:Rotating video without rotating AVCaptureConnection and in the middle of AVAssetWriter session 【发布时间】:2014-03-30 15:25:15 【问题描述】:

我正在使用PBJVision 来实现点击录制视频功能。该库尚不支持方向,因此我正在尝试对其进行设计。据我所知,有三种方法可以旋转视频-我需要帮助来确定最佳前进方式以及如何实现它.请注意,在点击录制片段之间可能会发生轮换。因此,在录制会话中,方向被锁定为用户点击按钮时的状态。下次用户点击按钮进行录制时,它应该将方向重新设置为设备的方向(因此生成的视频显示正面朝上)。

issue page on GitHub as well 中概述了这些方法

方法一 使用setVideoOrientation: 旋转AVCaptureConnection - 这会导致视频预览在每次切换时闪烁,因为这会切换它看起来的实际硬件。不酷,不可接受。

方法二 在用于编写视频的AVAssetWriterInput 对象上设置transform 属性。问题是,一旦资产编写者开始编写,transform 属性就无法更改,因此这只适用于视频的第一段。

方法三 使用类似这样的方式旋转附加的图像缓冲区:How to directly rotate CVImageBuffer image in ios 4 without converting to UIImage? 但它一直在崩溃,我什至不确定我是否在正确的树上吠叫。抛出了一个异常,除了我错误地使用了 vImageRotate90_ARGB8888 函数这一事实之外,我无法真正追溯到它。

我在上面链接到的 GitHub 问题页面上的解释更详细一些。欢迎任何建议 - 老实说,我在 AVFoundation 并没有丰富的经验,所以我希望有一些我什至不知道的神奇方法来做到这一点!

【问题讨论】:

您好,您找到解决方案了吗? 【参考方案1】:

根据Apple's documentation,方法 1 不是首选方法(“物理旋转缓冲区确实会带来性能成本,因此只有在必要时才请求旋转”)。方法 2 对我有用,但如果我在不支持转换“元数据”的应用程序上播放视频,则视频无法正确旋转。方法3是我做的。

我认为在您尝试将图像数据直接从vImageRotate... 传递到AVAssetWriterInputPixelBufferAdaptor 之前,它已经崩溃了。您必须先创建一个CVPixelBufferRef。这是我的代码:

captureOutput:didOutputSampleBuffer:fromConnection: 内部,我在将框架写入适配器之前旋转了框架:

if ([self.videoWriterInput isReadyForMoreMediaData])

    // Rotate buffer first and then write to adaptor
    CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    CVPixelBufferRef rotatedBuffer = [self correctBufferOrientation:sampleBuffer];
    [self.videoWriterInputAdaptor appendPixelBuffer:rotatedBuffer withPresentationTime:sampleTime];
    CVBufferRelease(rotatedBuffer);

执行vImage旋转的引用函数是:

/* rotationConstant:
 *  0 -- rotate 0 degrees (simply copy the data from src to dest)
 *  1 -- rotate 90 degrees counterclockwise
 *  2 -- rotate 180 degress
 *  3 -- rotate 270 degrees counterclockwise
 */

- (CVPixelBufferRef)rotateBuffer:(CMSampleBufferRef)sampleBuffer withConstant:(uint8_t)rotationConstant

    CVImageBufferRef imageBuffer        = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer, 0);        

    OSType pixelFormatType              = CVPixelBufferGetPixelFormatType(imageBuffer);
    NSAssert(pixelFormatType == kCVPixelFormatType_32ARGB, @"Code works only with 32ARGB format. Test/adapt for other formats!");

    const size_t kAlignment_32ARGB      = 32;
    const size_t kBytesPerPixel_32ARGB  = 4;

    size_t bytesPerRow                  = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t width                        = CVPixelBufferGetWidth(imageBuffer);
    size_t height                       = CVPixelBufferGetHeight(imageBuffer);

    BOOL rotatePerpendicular            = (rotateDirection == 1) || (rotateDirection == 3); // Use enumeration values here
    const size_t outWidth               = rotatePerpendicular ? height : width;
    const size_t outHeight              = rotatePerpendicular ? width  : height;

    size_t bytesPerRowOut               = kBytesPerPixel_32ARGB * ceil(outWidth * 1.0 / kAlignment_32ARGB) * kAlignment_32ARGB;

    const size_t dstSize                = bytesPerRowOut * outHeight * sizeof(unsigned char);

    void *srcBuff                       = CVPixelBufferGetBaseAddress(imageBuffer);

    unsigned char *dstBuff              = (unsigned char *)malloc(dstSize);

    vImage_Buffer inbuff                = srcBuff, height, width, bytesPerRow;
    vImage_Buffer outbuff               = dstBuff, outHeight, outWidth, bytesPerRowOut;

    uint8_t bgColor[4]                  = 0, 0, 0, 0;

    vImage_Error err                    = vImageRotate90_ARGB8888(&inbuff, &outbuff, rotationConstant, bgColor, 0);
    if (err != kvImageNoError) 
    
        NSLog(@"%ld", err);
    

    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

    CVPixelBufferRef rotatedBuffer      = NULL;
    CVPixelBufferCreateWithBytes(NULL,
                                 outWidth,
                                 outHeight,
                                 pixelFormatType,
                                 outbuff.data,
                                 bytesPerRowOut,
                                 freePixelBufferDataAfterRelease,
                                 NULL,
                                 NULL,
                                 &rotatedBuffer);

    return rotatedBuffer;


void freePixelBufferDataAfterRelease(void *releaseRefCon, const void *baseAddress)

    // Free the memory we malloced for the vImage rotation
    free((void *)baseAddress);

注意:您可能希望对rotationConstant 使用枚举。类似的东西(不要用MOVRotateDirectionUnknown 调用这个函数):

typedef NS_ENUM(uint8_t, MOVRotateDirection)

    MOVRotateDirectionNone = 0,
    MOVRotateDirectionCounterclockwise90,
    MOVRotateDirectionCounterclockwise180,
    MOVRotateDirectionCounterclockwise270,
    MOVRotateDirectionUnknown
;

注意:如果您需要IOSurface 支持,您应该使用CVPixelBufferCreate 而不是CVPixelBufferCreateWithBytes 并直接将字节数据传递给它:

NSDictionary *pixelBufferAttributes = @ (NSString *)kCVPixelBufferIOSurfacePropertiesKey : @ ;
CVPixelBufferCreate(kCFAllocatorDefault,
                    outWidth,
                    outHeight,
                    pixelFormatType,
                    (__bridge CFDictionaryRef)(pixelBufferAttributes),
                    &rotatedBuffer);

CVPixelBufferLockBaseAddress(rotatedBuffer, 0);
uint8_t *dest = CVPixelBufferGetBaseAddress(rotatedBuffer);
memcpy(dest, outbuff.data, bytesPerRowOut * outHeight);

CVPixelBufferUnlockBaseAddress(rotatedBuffer, 0);

【讨论】:

这看起来很棒,会试一试,让你知道它是怎么回事,谢谢! 警告:此方法适用于纵向视频,但不适用于横向视频。如果您想旋转横向视频(使用rotationConstant 0 或2),您需要在bytesPerRowOutoutbuffCVPixelBufferCreateWithBytes 中交换widthheight @JaiGovindani 你知道在哪里写吗? 没有项目变冷了,抱歉 :)【参考方案2】:

有一种简单又安全的方法。

#define degreeToRadian(x) (Double.pi * x / 180.0)

self.assetWriterInputVideo.transform = 
CGAffineTransformMakeRotation(CGFloat(degreeToRadian(-90))) ;

【讨论】:

来自问题:“问题是,一旦资产编写器开始写入,就无法更改转换属性” 不,这个transform 仅用于显示,并不会真正改变视频数据【参考方案3】:

方法 3 确实可以旋转视频的帧。 但我发现它会导致MM泄漏。为此,我尝试将函数移动到与合并视频帧相同的线程中。 它确实有效。 当您遇到问题时,请注意。

【讨论】:

以上是关于旋转视频而不旋转 AVCaptureConnection 并在 AVAssetWriter 会话中间的主要内容,如果未能解决你的问题,请参考以下文章

当方向改变而不旋转布局时如何旋转按钮?

当用户改变设备方向时,如何只旋转导航栏而不旋转视图?

如何仅旋转特定对象而不影响opengl中的其他对象?

旋转 UIButton titleLabel 而不剪裁

从中心而不是从左、中旋转

如何旋转图像视图而不滞后?