你的位置:首页 > 操作系统

[操作系统][iOS]关于视频方向的若干问题

一、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)degrees12 {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标签中增加 cnblogs_code">ffmpeg -i input.mp4 -c copy -metadata:s:v:0 rotate=90 output.mp4 
注:
如果愿意,写入非0、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结构中,颜色空间可以设置为RGB或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 components15   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

九、参考资料: 

http://www.rosoo.net/a/201006/9689.html
http://stackoverflow.com/questions/14167976/rotate-an-yuv-byte-array-on-android