iOS 视频/图片 方向问题及压缩转码

2017-05-16  本文已影响0人  念念不忘一个丫头的容

上传到服务器的图片,在电脑上打开发现都是横着的
查询原因发现因为图片中有一个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;
            }
        });
    }];
}
上一篇下一篇

猜你喜欢

热点阅读