AVFoundation - 为什么我不能正确地获得视频

时间:2014-02-18 06:33:08

标签: objective-c avfoundation

我正在使用AVCaptureSession从设备摄像头捕获视频,然后使用AVAssetWriterInputAVAssetTrack压缩/调整视频大小,然后再将其上传到服务器。最终视频将通过html5视频元素在网上观看。

我遇到了多个问题,试图让视频的方向正确。我的应用程序仅支持横向,所有捕获的视频都应横向显示。但是,我想允许用户将设备保持在横向方向(即左侧或右侧的主页按钮)。

我能够使用以下代码行以正确的方向显示视频预览

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

通过AVAssetWriterInput和朋友处理视频时问题就出现了。结果似乎并未考虑视频捕获的左右横向模式.IOW,有时视频会颠倒过来。经过一些谷歌搜索后,我发现很多人建议以下代码行解决这个问题

writerInput.transform = videoTrack.preferredTransform;

......但这似乎不起作用。经过一些调试后,我发现videoTrack.preferredTransform始终是相同的值,无论视频的捕获方向如何。

我尝试手动跟踪视频的捕获方向,并根据需要将writerInput.transform设置为CGAffineTransformMakeRotation(M_PI)。这解决了问题!!!

...几分

当我在设备上查看结果时,此解决方案按预期工作。录制时,无论左右方向如何,视频都是正面向上的。不幸的是,当我在另一个浏览器中观看完全相同的视频时(Mac书上的chrome),它们都是颠倒的!?!?!?

我做错了什么?

修改

这是一些代码,如果它有用......

-(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 */
     }];
}

3 个答案:

答案 0 :(得分:8)

问题是修改writerInput.transform属性只会在视频文件元数据中添加一个标记,指示视频播放器在播放期间旋转文件。这就是为什么视频在你的设备上以正确的方向播放的原因(我猜他们也可以在Quicktime播放器中正确播放)。

相机捕获的像素缓冲区仍然以捕获它们的方向布局。许多视频播放器不会检查首选方向元数据标签,只会以原始像素方向播放文件。

如果您希望用户能够以横向模式录制持有手机的视频,则需要在压缩前通过对每个视频帧的CVPixelBuffer执行变换,在AVCaptureSession级别对此进行纠正。 Apple Q& A涵盖了它(请查看AVCaptureVideoOutput文档): https://developer.apple.com/library/ios/qa/qa1744/_index.html

调查上述链接是解决问题的正确方法。另一种解决同样问题的 fast n'dirty 方法是将应用程序的录制UI锁定为只有一个横向,然后使用ffmpeg旋转所有视频服务器端。

答案 1 :(得分:1)

为了压缩/调整视频大小,我们可以使用AVAssetExportSession。

  • 我们可以播放持续时间为3.30分钟的视频。
  • 如果视频时长超过3.30分钟,则会显示内存警告。
  • 由于此处我们没有对视频使用任何变换,因此视频将在录制时保持原样。
  • 以下是压缩视频的示例代码。
  • 我们可以在压缩前和压缩后检查视频大小。

{

    -(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;
     }
 }];}

答案 2 :(得分:0)

如果它对任何人都有帮助,这里是我最终得到的代码。我最终不得不对视频进行处理,而不是作为后期处理步骤。这是一个管理捕获的辅助类。

接口

#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

实施(带一点编辑和一些小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
相关问题