【问题标题】:How do I set the orientation for a frame-by-frame-generated video using AVFoundation?如何使用 AVFoundation 为逐帧生成的视频设置方向?
【发布时间】:2024-01-19 02:14:01
【问题描述】:

我正在编写一个 iPhone 应用程序,它从摄像头获取视频,通过一些 OpenGL 着色器代码运行它,然后使用 AVFoundation 将输出写入视频文件。该应用程序以横向(任一)方向运行,因此录制的所有视频都应为横向。

我在开始录制之前使用的当前代码来正确获取视频是:

[[self videoWriterInput] setTransform:CGAffineTransformScale(CGAffineTransformMakeRotation(M_PI), -1.0, 1.0)];

其中 videoWriterInput 是 AVAssetWriterInput 的一个实例,其目的是补偿横向模式和 OpenGL 的反向方向。

这会生成在 Quicktime 播放器上下载和播放时可以正确播放的视频。但是,如果我将录制的视频添加到 iPhone 照片库,缩略图会正确显示,但如果手机处于横向状态,视频会旋转 90 度播放。如果手机是纵向的,视频可以正常播放,但会被水平裁剪以适应纵向尺寸。

根据this Apple tech note,我用于处理视频帧的AVCaptureVideoDataOutput 的捕获输出不支持设置视频方向。

有没有人成功录制了横向生成的视频,可以添加到 iPhone 库中并在横向中正确播放,如果是,如何?

【问题讨论】:

  • 在从帧缓冲区中读取像素之前,您是否有任何理由不能在 OpenGL 中进行旋转以获得正确的方向?这样您就不必担心放置在 MOV/MP4 标头中的转换。默认设置就可以了。
  • 感谢这个想法。然而,它的问题是屏幕上的实时视图是正确的 - 只有当我将录制的视频保存到 iPhone 库并播放它,甚至录制在桌面上正确播放时,才会出现不正确的视图。

标签: iphone ios camera orientation avfoundation


【解决方案1】:

您的帖子让我大开眼界,了解 Apples Videos 应用如何播放视频。我用我的应用程序在四个方向上记录了几个项目。他们都以正确的方向播放。我刚刚注意到视频应用不像相册应用中的播放器那样支持旋转。视频应用程序希望您横向握住设备(至少是我的 iPod touch)。我做了一些纵向录制,将它们添加到 iTunes,所有的,包括使用 Apple 的相机应用程序创建的,在将设备旋转到纵向时都没有旋转。

无论如何...

我的应用程序是一个延时应用程序,它不会像您那样对帧进行任何额外的处理,所以 YMMV 在下面。我设置了我的应用程序,使其在设备旋转时不会旋转窗口。这样我总是处理设备的一个方向。我使用 AVFoundation 从视频流中抓取每第 N 帧并将其写出来。

在设置录制时,我会执行以下操作。

inputWriterBuffer = [AVAssetWriterInput assetWriterInputWithMediaType: AVMediaTypeVideo outputSettings: outputSettings];
    // I call this explicitly before recording starts. Video plays back the right way up.
[self detectOrientation];
inputWriterBuffer.transform = playbackTransform;

detectOrientation 调用以下方法。为了清楚起见,我在这里减少了实际代码。在我的应用程序中,我还旋转了一些按钮,因此请注意它们没有得到相同的转换。需要注意的是我是如何设置playbackTransform ivar的。

-(void) detectOrientation {
CGAffineTransform buttonTransform;

switch ([[UIDevice currentDevice] orientation]) {
    case UIDeviceOrientationUnknown:
        NULL;
    case UIDeviceOrientationFaceUp:
        NULL;
    case UIDeviceOrientationFaceDown:
        NULL;
        break;
    case UIDeviceOrientationPortrait:
        [UIButton beginAnimations: @"myButtonTwist" context: nil];
        [UIButton setAnimationDuration: 0.25];
        buttonTransform = CGAffineTransformMakeRotation( ( 0 * M_PI ) / 180 );
        recordingStarStop.transform = buttonTransform;
        [UIButton commitAnimations];            

        playbackTransform = CGAffineTransformMakeRotation( ( 90 * M_PI ) / 180 );
        break;
    case UIDeviceOrientationLandscapeLeft:
        [UIButton beginAnimations: @"myButtonTwist" context: nil];
        [UIButton setAnimationDuration: 0.25];
        buttonTransform = CGAffineTransformMakeRotation( ( 90 * M_PI ) / 180 );
        recordingStarStop.transform = buttonTransform;
        [UIButton commitAnimations];            

        // Transform depends on which camera is supplying video
        if (theProject.backCamera == YES) playbackTransform = CGAffineTransformMakeRotation( 0 / 180 );
        else playbackTransform = CGAffineTransformMakeRotation( ( -180 * M_PI ) / 180 );

        break;
    case UIDeviceOrientationLandscapeRight:
        [UIButton beginAnimations: @"myButtonTwist" context: nil];
        [UIButton setAnimationDuration: 0.25];
        buttonTransform = CGAffineTransformMakeRotation( ( -90 * M_PI ) / 180 );
        recordingStarStop.transform = buttonTransform;
        [UIButton commitAnimations];

        // Transform depends on which camera is supplying video
        if (theProject.backCamera == YES) playbackTransform = CGAffineTransformMakeRotation( ( -180 * M_PI ) / 180 );
        else playbackTransform = CGAffineTransformMakeRotation( 0 / 180 );

        break;
    case UIDeviceOrientationPortraitUpsideDown:
        [UIButton beginAnimations: @"myButtonTwist" context: nil];
        [UIButton setAnimationDuration: 0.25];
        buttonTransform = CGAffineTransformMakeRotation( ( 180 * M_PI ) / 180 );
        recordingStarStop.transform = buttonTransform;
        [UIButton commitAnimations];

        playbackTransform = CGAffineTransformMakeRotation( ( -90 * M_PI ) / 180 );
        break;
    default:
        playbackTransform = CGAffineTransformMakeRotation( 0 / 180 ); // Use the default, although there are likely other issues if we get here.
        break;
}
}

附带说明,由于我希望在设备旋转时调用该方法,并且我已关闭自动旋转,因此我的 viewDidLoad 方法中有以下内容。

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectOrientation) name:@"UIDeviceOrientationDidChangeNotification" object:nil];

这是我在this SOF Q&A 找到的提示。

【讨论】:

  • 这个答案确实为我的应用带来了正确的答案。对于 OpenGL 转换的视频,我仍然需要应用缩放来翻转视频,但关键是我需要旋转 -M_PI 而不是 M_PI。谢谢!
【解决方案2】:

简单多了……

在您的顶点着色器中:

统一浮动首选旋转; . . . mat4 旋转矩阵 = mat4( cos(preferredRotation), -sin(preferredRotation), 0.0, 0.0, sin(preferredRotation), cos(preferredRotation), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);

/* 90-degrees
mat4 rotationMatrix = mat4( cos(preferredRotation), -sin(preferredRotation), (1.0 - cos(preferredRotation)) - sin(preferredRotation), 0.0,
                           -sin(preferredRotation),  cos(preferredRotation), 0.0, 0.0,
                           0.0,                     0.0, 1.0, 0.0,
                           0.0,                     0.0, 0.0, 1.0);

在调用着色器的视图或视图控制器中:

if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { CGAffineTransform preferredTransform = [videoTrack preferredTransform]; self.playerView.preferredRotation = -1 * atan2(preferredTransform.b, preferredTransform.a);

...等等...

【讨论】: