• [iOS]关于视频方向的若干问题


    版本:
    OS X 10.10.5
    Xcode 6.4(6E35b)
    iOS >= 7 

    一、MOV/MP4视频文件中的Rotation元数据

    iOS上内置相机应用录制的mov/mp4视频可能产生一个Rotation元数据,表示录制视频时摄像头旋转到了多少角度。其值一般为这四个:0、90、180或270。类似于图片文件的Exif信息中的Orientation元数据。
    Rotation元数据用于播放器确定渲染视频的方向,但有的播放器会对其视而不见。稍后会测试几种常见的播放器/播放控件对Rotation元数据的支持。
    注:
    实际上视频文件的Rotation元数据并不是保存的角度值,不过如果只关心角度问题而不是图像拉伸之类的,可以这样简单理解。关于如何获取Rotation元数据角度值,有兴趣的可以参看vlc的源码
     
    下面用MediaInfo看看用iPhone相机应用使用后置摄像头录制的两个视频,观察其Rotation元数据。请留意文件名分别为IMG_1427.MOV和IMG_1428.MOV,后文也会用这两个文件做对比。
    1、使用后置摄像头在Portrait(竖屏,Home键在下边)模式时录制的视频,其Rotation值为90。
    (图一:Rotation值为90)
     
    2、使用后置摄像头在LandscapeRigth(横屏,Home键在右边)模式时录制的视频,则无Rotation元数据,或者说Rotation值为0。
    (图二:无Rotation值或者说Rotation值为0)
      
    关于Rotation的0、90、180和270这四个角度值可以这样理解:LandscapeRigth为0度;以Home键或摄像头为圆心,顺时针旋转到Portrait为90度;旋转到LandscapeLeft为180度;旋转到PortraitUpsideDown为270度。
     
    这里先在OS X 10.10.4和Windows 8上看看这两个视频文件的属性:
    1、将手机里的视频文件导出到OS X,并在Finder中看预览,两个文件的显示方向都是正确的。再查看Rotation值为90的IMG_1427.MOV视频文件的属性。显示其尺寸为1080*1920,而不是1920*1080。但不要被这个假象欺骗了,视频的实际尺寸还是1920*1080。最后看没有Rotation值或者说Rotation值为0的IMG_1428.MOV视频文件,显示其尺寸为1920*1080,一切正常。使用QuickTime播放,能正确识别出两个视频的方向。
     
    (图三) 
    2、在Windows资源管理器中看预览,IMG_1427.MOV和IMG_1428.MOV的显示方向都是正确的;再查看两个文件的属性,尺寸都显示为1920*1080;使用Windows Media Player播放,能正确识别出两个视频的方向。

    二、常见视频播放器对方向的识别

    iOS相册调出的播放器和Win8上的Windows Media Player能够正确识别出MOV/MP4的方向,即实际尺寸为1920*1080的、Rotation值为90的IMG_1427.MOV视频能够按1080*1920的尺寸并调整方向进行渲染;没有Rotation值或者说Rotation值为0的IMG_1428.MOV视频按1920*1080的尺寸并按实际方向进行渲染。Andriod也存在类似情况。
    VLC for OS X(why?)和iOS的MPMoviePlayerViewControlle对Rotation没有识别,它们总是按实际尺寸和默认方向进行渲染。对于MPMoviePlayerViewControlle下面有解决方案。
    Safari浏览器调出的播放器应该也是MPMoviePlayerViewController,所以也无法正确识别方向。 

    三、MPMoviePlayerViewController控制视频方向

    需要额外的参数来确定视频的方向,然后旋转播放器,达到各种视频——mov/mp4/m3u8等——都可以正确播放的目的。
     
     1 //…...
     2     NSString * url = @"http://www.yourdomain.com/Videos/1.m3u8";
     3     MPMoviePlayerViewController * vc = [[MPMoviePlayerViewController alloc] init];
     4     vc.moviePlayer.contentURL = [NSURL URLWithString:url];
     5     // 这里播放一个Rotation为90的视频,即Home键在下录制的视频
     6     [self rotateVideoView:vc degrees:90];
     7     [self presentMoviePlayerViewControllerAnimated:vc];
     8     [vc.moviePlayer play];
     9  
    10 //…...
    11 - (void)rotateVideoView:(MPMoviePlayerViewController *)movePlayerViewController degrees:(NSInteger)degrees
    12 {
    13     if(degrees==0||degrees==360) return;
    14     if(degrees<0) degrees = (degrees % 360) + 360;
    15     if(degrees>360) degrees = degrees % 360;
    16     // MPVideoView在iOS8中Tag为1002,不排除苹果以后更改的可能性。参考递归查看View层次结构的lldb命令: (lldb) po [movePlayerViewController.view recursiveDescription]
    17     UIView *videoView = [movePlayerViewController.view viewWithTag:1002];
    18     if ([videoView isKindOfClass:NSClassFromString(@"MPVideoView")]) {
    19         videoView.transform = CGAffineTransformMakeRotation(M_PI * degrees / 180.0);
    20         videoView.frame = movePlayerViewController.view.bounds;
    21     }
    22 }
    View Code

    改为Category:

     1 #import "MPMoviePlayerViewController+Rotation.h"
     2 
     3 @implementation MPMoviePlayerViewController (Rotation)
     4 
     5 - (void)rotateVideoViewWithDegrees:(NSInteger)degrees
     6 {
     7     if(degrees==0||degrees==360) return;
     8     if(degrees<0) degrees = (degrees % 360) + 360;
     9     if(degrees>360) degrees = degrees % 360;
    10    
    11     // MPVideoView在iOS8中Tag为1002,不排除苹果以后更改的可能性。参考递归查看View层次结构的lldb命令: (lldb) po [movePlayerViewController.view recursiveDescription]
    12     UIView *videoView = [self.view viewWithTag:1002];
    13     if ([videoView isKindOfClass:NSClassFromString(@"MPVideoView")]) {
    14         videoView.transform = CGAffineTransformMakeRotation(M_PI * degrees / 180.0);
    15         videoView.frame = self.view.bounds;
    16     }
    17 }
    18 
    19 @end

    四、HTML5控制视频方向 

    在video标签中增加 style="-webkit-transform: rotate(90deg);” ,不过控件也被旋转了。这就需要将默认播放控件隐藏了并且自绘控件,此略。

    五、使用ffmpeg写入Rotation元数据 

    对于没有Rotation元数据的mp4文件,可通过ffmpeg等工具写入。比如视频需要顺时针旋转90度显示:
     ffmpeg -i input.mp4 -c copy -metadata:s:v:0 rotate=90 output.mp4 
    注:
    如果愿意,写入非0、90、180或270的值,比如45之类的也是可以的。 

    六、获取视频方向(角度) 

    + (NSUInteger)degressFromVideoFileWithURL:(NSURL *)url
    {
        NSUInteger degress = 0;
       
        AVAsset *asset = [AVAsset assetWithURL:url];
        NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
        if([tracks count] > 0) {
            AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
            CGAffineTransform t = videoTrack.preferredTransform;
           
            if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
                // Portrait
                degress = 90;
            }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
                // PortraitUpsideDown
                degress = 270;
            }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
                // LandscapeRight
                degress = 0;
            }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
                // LandscapeLeft
                degress = 180;
            }
        }
       
        return degress;
    }

    七、按正确方向对视频进行截图

    关键点是将AVAssetImageGrnerator对象的appliesPreferredTrackTransform属性设置为YES。
     
     1 + (UIImage *)extractImageFromVideoFileWithUrl:(NSURL *)url
     2 {
     3     NSDictionary *opts = @{AVURLAssetPreferPreciseDurationAndTimingKey:@(NO)};
     4     AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:opts];
     5     AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
     6     // 应用方向
     7     gen.appliesPreferredTrackTransform = YES;
     8     CMTime time = CMTimeMakeWithSeconds(1, 60);
     9     NSError *error = nil;
    10     CMTime actualTime;
    11     CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    12     if(error)
    13     {
    14         DLog(@"%@ %@",__FUNCTION_FILE_LINE__,error);
    15         return nil;
    16     }
    17     UIImage *thumb = [[UIImage alloc] initWithCGImage:image];
    18     CGImageRelease(image);
    19    
    20     return thumb;
    21 } 

    八、实时视频的方向处理

    使用AVFoundation制作自定义相机时,采集出来的视频帧保存在CMSampleBufferRef结构中,颜色空间可以设置为sRGB或YUV。进行一些内存操作就可实现旋转。以下代码是针对YUV的。 
    注:
    这种涉及大量内存拷贝的操作,实际应用中要权衡其利弊。以下代码未经过测试。
    1、RGB24旋转90度  
     1 // RGB24旋转90度
     2 void RGB24Rotate90(int8_t *des, const int8_t *src, int width, int height)
     3 {
     4     if(!des || !src) return;
     5    
     6     int n = 0;
     7     int linesize = width * 3;
     8     int i, j;
     9     // 逆时针旋转
    10     for (j = width; j > 0; j--) {
    11         for (i = 0; i < height; i++) {
    12             memccpy(&des[n], &src[linesize * i + j * 3 - 3], 0, 3);
    13             n += 3;
    14         }
    15     }
    16     /*
    17     // 顺时针旋转
    18     for (j = 0 ; j < width; j++) {
    19         for (i = height; i > 0; i--) {
    20             memccpy(&des[n], &src[linesize * (i - 1) + j * 3 - 3], 0, 3);
    21             n += 3;
    22         }
    23     }
    24     */
    25 }
    View Code

     
    2、RGB24旋转90度  
     1 // YUV420旋转90度
     2 void YUV420Rotate90(int8_t *des, const int8_t *src, int width, int height)
     3 {
     4     int i = 0, j = 0, n = 0;
     5     int hw = width / 2, hh = height / 2;
     6    
     7     const int8_t *ptmp = src;
     8     for (j = width; j > 0; j--) {
     9         for (i = 0; i < height; i++) {
    10             des[n++] = ptmp[width * i + j];
    11         }
    12     }
    13    
    14     ptmp = src + width * height;
    15     for (j = hw; j > 0; j--) {
    16         for (i = 0; i < hh; i++) {
    17             des[n++] = ptmp[hw * i + j];
    18         }
    19     }
    20    
    21     ptmp = src + width * height * 5 / 4;
    22     for (j = hw; j > 0; j--) {
    23         for (i = 0; i < hh; i++) {
    24             des[n++] = ptmp[hw * i + j];
    25         }
    26     }
    27 }
    View Code

    或:

     1 int8_t[] rotateYUV420Degree90(int8_t[] data, int imageWidth, int imageHeight)
     2 {
     3     int8_t [] yuv = new int8_t[imageWidth*imageHeight*3/2];
     4     // Rotate the Y luma
     5     int i = 0;
     6     for(int x = 0;x < imageWidth;x++)
     7     {
     8         for(int y = imageHeight-1;y >= 0;y--)
     9         {
    10             yuv[i] = data[y*imageWidth+x];
    11             i++;
    12         }
    13     }
    14     // Rotate the U and V color components
    15     i = imageWidth*imageHeight*3/2-1;
    16     for(int x = imageWidth-1;x > 0;x=x-2)
    17     {
    18         for(int y = 0;y < imageHeight/2;y++)
    19         {
    20             yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+x];
    21             i--;
    22             yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
    23             i--;
    24         }
    25     }
    26     return yuv;
    27 }
    View Code

    九、参考资料: 

     
     
  • 相关阅读:
    idea的tomcat配置
    idea设置类文件的头部信息
    设置idea注释颜色
    Idea设置字体
    python 全栈开发,Day11(函数名应用,闭包,装饰器初识,带参数以及带返回值的装饰器)
    python 全栈开发,Day10(动态参数,命名空间,作用域,函数嵌套)
    python 全栈开发,Day9(函数的初始,返回值,传参,三元运算)
    python 全栈开发,Day8(文件操作)
    python 全栈开发,Day7(元组转换,列表以及字典的坑,集合,关系测试,深浅copy,编码补充)
    python 全栈开发,Day6补充(is,小数据池,编码转换)
  • 原文地址:https://www.cnblogs.com/alby/p/4610077.html
Copyright © 2020-2023  润新知