iOS 视频/图片 方向问题及压缩转码
上传到服务器的图片,在电脑上打开发现都是横着的
查询原因发现因为图片中有一个Exif头信息,上传的时候直接把UIImage转为NSData后它就丢失了。iPhone拍照以横屏作为正方向,Windows以竖直作为正方向。偏偏这个方向信息就存储在这个Exif中,丢失了方向的照片在Windows上就……迷路了 : )
我翻开历史一查,这历史没有年代,歪歪斜斜的每页上都写着“仁义道德”几个字。我横竖睡不着,仔细看了半夜,才从字缝里看出字来,满本都写着两个字是“吃人”! —— 鲁迅
怎么解决?
1、 在UIImage转换为NSData的时候加入Exif使其不丢失
2、 传输NSData的时候将方向一同传入服务器,由接收方处理
3、 上传前先读取方向信息,将图片进行旋转
第一条肯定是最好最合理的,不过Windows上还存在不读取Exif的照片查看器,并且也着实没找到相关方法只能另辟蹊径了。
第二条过于复杂,而且不仅自己没处理好问题还影响了接受方的逻辑。
最后只好按第三条,自己处理好再给别人使用。
一、将图片按照正确方向旋转
图片很好处理,Graphics在绘制UIImage的时候会读取Exif绘制出正确的图像。利用这一点可以用很少的代码获取修正后的UIImage。
我们给UIImage写一个分类来方便使用
- (UIImage *)normalizedImage {
if (self.imageOrientation == UIImageOrientationUp) return self;
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
[self drawInRect:(CGRect){0, 0, self.size}];
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalizedImage;
}
这样就很简单地解决了转化为NSData之后图片Exif信息丢失导致图片旋转的问题,然而还没高兴起来就发现视频同样有这个事儿……
在视频文件的信息中同样包含一个Rotation信息,它包含了此视频拍摄时的方向,视频播放时播放器会读取它然后用正确的方向播放,如果它丢了
你的产品经理会教你如何做人 —— 百里潋長
万幸的是AVAssetExportSession可以对视频压缩转码,同时可以编辑视频方向,正好视频上传有压缩和将wav转码为mp4的需求,这下可以顺带解决。
二、视频转码压缩及方向修正
使用AVAsset读取到文件,然后访问AVAssetTrack粗暴直接地做一个判断就可以得到方向
+ (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;
}
AVAssetExportSession需要一个AVComposition,我们将上面的代码整合一下,利用获取到的方向信息来决定视频大小
- (AVMutableVideoComposition *)getVideoComposition:(AVAsset *)asset {
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
CGSize videoSize = videoTrack.naturalSize;
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) ||
(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0)){
videoSize = CGSizeMake(videoSize.height, videoSize.width);
}
}
composition.naturalSize = videoSize;
videoComposition.renderSize = videoSize;
videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600);
AVMutableCompositionTrack *compositionVideoTrack;
compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
AVMutableVideoCompositionLayerInstruction *layerInst;
layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
[layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
inst.layerInstructions = [NSArray arrayWithObject:layerInst];
videoComposition.instructions = [NSArray arrayWithObject:inst];
return videoComposition;
}
最后只需要在压缩转码的时候将它添入AVAssetExportSession的videoComposition中,这里我将输出的视频文件写入到Temp下的VideoCompression目录中,文件名叫VideoCompressionTemp.mp4
- (void)lowQuailtyWithInputURL:(NSURL *)inputURL blockHandler:(void (^)(AVAssetExportSession *session, NSURL *compressionVideoURL))handler {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
NSString *path = [NSString stringWithFormat:@"%@VideoCompression/",NSTemporaryDirectory()];
NSFileManager *fileManage = [[NSFileManager alloc] init]; static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
if(![fileManage fileExistsAtPath:path]){
[fileManage createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
});
if([fileManage fileExistsAtPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path]]){
[fileManage removeItemAtPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path] error:nil];
}
NSURL *compressionVideoURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path]];
session.outputURL = compressionVideoURL;
session.outputFileType = AVFileTypeMPEG4;
session.shouldOptimizeForNetworkUse = YES;
session.videoComposition = [self getVideoComposition:asset];
[session exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(),^{
switch ([session status]) {
case AVAssetExportSessionStatusFailed:{
NSLog(@"Export failed: %@ : %@", [[session error] localizedDescription], [session error]);
handler(session, nil);
break;
}case AVAssetExportSessionStatusCancelled:{
NSLog(@"Export canceled");
handler(session, nil);
break;
}default:
handler(session,compressionVideoURL);
break;
}
});
}];
}