[iOS]關於視頻方向的若干問題

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

1、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,後文也會用這兩個文件作對比。
一、使用後置攝像頭在Portrait(豎屏,Home鍵在下邊)模式時錄製的視頻,其Rotation值爲90。
(圖一:Rotation值爲90)
 
二、使用後置攝像頭在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上看看這兩個視頻文件的屬性:
一、將手機裏的視頻文件導出到OS X,並在Finder中看預覽,兩個文件的顯示方向都是正確的。再查看Rotation值爲90的IMG_1427.MOV視頻文件的屬性。顯示其尺寸爲1080*1920,而不是1920*1080。但不要被這個假象欺騙了,視頻的實際尺寸仍是1920*1080。最後看沒有Rotation值或者說Rotation值爲0的IMG_1428.MOV視頻文件,顯示其尺寸爲1920*1080,一切正常。使用QuickTime播放,能正確識別出兩個視頻的方向。
 
(圖三) 
二、在Windows資源管理器中看預覽,IMG_1427.MOV和IMG_1428.MOV的顯示方向都是正確的;再查看兩個文件的屬性,尺寸都顯示爲1920*1080;使用Windows Media Player播放,能正確識別出兩個視頻的方向。

2、常見視頻播放器對方向的識別

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,因此也沒法正確識別方向。 

3、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:html

 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

4、HTML5控制視頻方向 

在video標籤中增長  style="-webkit-transform: rotate(90deg);」 ,不過控件也被旋轉了。這就須要將默認播放控件隱藏了而且自繪控件,此略。

5、使用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之類的也是能夠的。  

6、獲取視頻方向(角度) 

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

7、按正確方向對視頻進行截圖

關鍵點是將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 } 

8、實時視頻的方向處理

使用AVFoundation製做自定義相機時,採集出來的視頻幀保存在CMSampleBufferRef結構中,顏色空間能夠設置爲sRGB或YUV。進行一些內存操做就可實現旋轉。如下代碼是針對YUV的。 
注:
這種涉及大量內存拷貝的操做,實際應用中要權衡其利弊。如下代碼未通過測試。
一、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

 
二、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

或:android

 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

9、參考資料: 

相關文章
相關標籤/搜索