iOS多张图片合成视频

作者: 路飞_Luck | 来源:发表于2017-08-12 10:41 被阅读231次
    引言:随着多媒体的普及,越来越多的短视频被人们所喜爱,接受,传播,所以快速而有效的合成高质量的视频成为刚需。

    本文参考Skylpy作者,在原有基础上进行扩展,编码整理。

    1.因为涉及到视频合成和播放,所以需求先引入一些和视频相关的资源库
    #import <AVKit/AVKit.h>
    #import <MediaPlayer/MediaPlayer.h>
    #import <AVFoundation/AVFoundation.h>
    

    然后定义一些宏,方便UI布局

    #define WWScreamW [UIScreen mainScreen].bounds.size.width
    #define WWScreamH [UIScreen mainScreen].bounds.size.height
    

    接着定义一些变量

    @interface ViewController () {
        NSMutableArray*imageArr;    //未压缩的图片
        NSMutableArray*imageArray;  //经过压缩的图片
    }
    
    //视频地址
    @property(nonatomic,strong)NSString*theVideoPath;
    //合成进度
    @property(nonatomic,strong)UILabel *ww_progressLbe;
    
    2.定义一个方法用于视图布局
    - (void)ww_setupView {
        
        //视频合成按钮
        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [button setBounds:CGRectMake(0,0,WWScreamW * 0.25,50)];
        button.center = CGPointMake(WWScreamW * 0.25, WWScreamH * 0.15);
        [button setTitle:@"视频合成"forState:UIControlStateNormal];
        [button addTarget:self action:@selector(testCompressionSession)forControlEvents:UIControlEventTouchUpInside];
        button.backgroundColor = [UIColor redColor];
        [self.view addSubview:button];
        
        //视频播放按钮
        UIButton *button1=[UIButton buttonWithType:UIButtonTypeRoundedRect];
        [button1 setBounds:CGRectMake(0,0,WWScreamW * 0.25,50)];
        button1.center = CGPointMake(WWScreamW * 0.75, WWScreamH * 0.15);
        [button1 setTitle:@"视频播放"forState:UIControlStateNormal];
        [button1 addTarget:self action:@selector(playAction)forControlEvents:UIControlEventTouchUpInside];
        button1.backgroundColor = [UIColor redColor];
        [self.view addSubview:button1];
         
        //视频合成播放进度提示文本框
        UILabel *lbe = [[UILabel alloc]init];
        lbe.frame = CGRectMake(0, 0, WWScreamW * 0.25, 25);
        lbe.center = CGPointMake(WWScreamW * 0.5, WWScreamH * 0.15);
        lbe.textColor = [UIColor blackColor];
        lbe.textAlignment = NSTextAlignmentCenter;
        lbe.text = @"准备就绪";
        lbe.font = [UIFont systemFontOfSize:12];
        self.ww_progressLbe = lbe;
        [self.view addSubview:lbe];
    }
    
    3.定义一个方法用于赋值数据
    - (void)ww_setupInit {
        
        imageArray = [[NSMutableArray alloc]init];
        imageArr = [[NSMutableArray alloc]init];
        
        NSString *name = @"";
        UIImage *img = nil;
        
        //实先准备21张图片,命名为0.jpg至21.jpg
        for (int i = 0; i < 22; i++) {
            name = [NSString stringWithFormat:@"%d",i];
            img = [UIImage imageNamed:name];
            [imageArr addObject:img];
        }
       
        //对图片进行裁剪,方便合成等比例视频
        for (int i = 0; i < imageArr.count; i++) {
            
            UIImage *imageNew = imageArr[i];
            
            //设置image的尺寸
            CGSize imgeSize = CGSizeMake(320, 480);
            
            //对图片大小进行压缩--
            imageNew = [self imageWithImage:imageNew scaledToSize:imgeSize];
            
            [imageArray addObject:imageNew];
        }
    }
    

    对图片进行压缩方法如下

    -(UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
    
    {
        //    新创建的位图上下文 newSize为其大小
        UIGraphicsBeginImageContext(newSize);
        //    对图片进行尺寸的改变
        [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
        
        //    从当前上下文中获取一个UIImage对象  即获取新的图片对象
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        
        UIGraphicsEndImageContext();
        
        return newImage;
    }
    
    4.视频合成按钮点击操作事件
    //视频合成按钮点击操作
    - (void)testCompressionSession {
        
        //设置mov路径
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
        
        NSString *moviePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov",@"test"]];
        
        self.theVideoPath=moviePath;
        
        //定义视频的大小320 480 倍数
        CGSize size = CGSizeMake(320,480);
        
        NSError *error = nil;
        
        //    转成UTF-8编码
        unlink([moviePath UTF8String]);
        
        NSLog(@"path->%@",moviePath);
        
        //     iphone提供了AVFoundation库来方便的操作多媒体设备,AVAssetWriter这个类可以方便的将图像和音频写成一个完整的视频文件
        
        AVAssetWriter *videoWriter = [[AVAssetWriter alloc]initWithURL:[NSURL fileURLWithPath:moviePath]fileType:AVFileTypeQuickTimeMovie error:&error];
        
        NSParameterAssert(videoWriter);
        
        if(error) {
            NSLog(@"error =%@",[error localizedDescription]);
            return;
        }
        
        //mov的格式设置 编码格式 宽度 高度
        NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264,AVVideoCodecKey,
                                         
                                         [NSNumber numberWithInt:size.width],AVVideoWidthKey,
                                         
                                         [NSNumber numberWithInt:size.height],AVVideoHeightKey,nil];
        
        AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
        
        NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_32ARGB],kCVPixelBufferPixelFormatTypeKey,nil];
        
        //    AVAssetWriterInputPixelBufferAdaptor提供CVPixelBufferPool实例,
        //    可以使用分配像素缓冲区写入输出文件。使用提供的像素为缓冲池分配通常
        //    是更有效的比添加像素缓冲区分配使用一个单独的池
        AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
        
        NSParameterAssert(writerInput);
        
        NSParameterAssert([videoWriter canAddInput:writerInput]);
        
        if([videoWriter canAddInput:writerInput]){
            
            NSLog(@"11111");
            
        }else{
            
            NSLog(@"22222");
            
        }
        
        [videoWriter addInput:writerInput];
        
        [videoWriter startWriting];
        
        [videoWriter startSessionAtSourceTime:kCMTimeZero];
        
        //合成多张图片为一个视频文件
        
        dispatch_queue_t dispatchQueue = dispatch_queue_create("mediaInputQueue",NULL);
        
        int __block frame = 0;
        
        [writerInput requestMediaDataWhenReadyOnQueue:dispatchQueue usingBlock:^{
            
            while([writerInput isReadyForMoreMediaData]) {
                
                if(++frame >= [imageArray count] * 10) {
                    [writerInput markAsFinished];
                    
                    [videoWriter finishWritingWithCompletionHandler:^{
                        NSLog(@"完成");
                        
                        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                            
                            self.ww_progressLbe.text = @"视频合成完毕";
                            
                        }];
    
                    }];
                    break;
                }
                
                CVPixelBufferRef buffer = NULL;
                
                int idx = frame / 10;
                
                NSLog(@"idx==%d",idx);
                NSString *progress = [NSString stringWithFormat:@"%0.2lu",idx / [imageArr count]];
                
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    
                    self.ww_progressLbe.text = [NSString stringWithFormat:@"合成进度:%@",progress];
                    
                }];
    
                
                buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:[[imageArray objectAtIndex:idx]CGImage]size:size];
                
                if(buffer){
                    
                    //设置每秒钟播放图片的个数
                    if(![adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(frame,10)]) {
                        
                        NSLog(@"FAIL");
                        
                    } else {
                        
                        NSLog(@"OK");
                    }
                    
                    CFRelease(buffer);
                }
            }
        }];
    }
    

    由图片生成像素图片类型的方法如下

    - (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image size:(CGSize)size {
        
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                               
                               [NSNumber numberWithBool:YES],kCVPixelBufferCGImageCompatibilityKey,
                               
                               [NSNumber numberWithBool:YES],kCVPixelBufferCGBitmapContextCompatibilityKey,nil];
        
        CVPixelBufferRef pxbuffer = NULL;
        
        CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,size.width,size.height,kCVPixelFormatType_32ARGB,(__bridge CFDictionaryRef) options,&pxbuffer);
        
        NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
        
        CVPixelBufferLockBaseAddress(pxbuffer,0);
        
        void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
        
        NSParameterAssert(pxdata !=NULL);
        
        CGColorSpaceRef rgbColorSpace=CGColorSpaceCreateDeviceRGB();
        
        //    当你调用这个函数的时候,Quartz创建一个位图绘制环境,也就是位图上下文。当你向上下文中绘制信息时,Quartz把你要绘制的信息作为位图数据绘制到指定的内存块。一个新的位图上下文的像素格式由三个参数决定:每个组件的位数,颜色空间,alpha选项
        
        CGContextRef context = CGBitmapContextCreate(pxdata,size.width,size.height,8,4*size.width,rgbColorSpace,kCGImageAlphaPremultipliedFirst);
        
        NSParameterAssert(context);
        
        //使用CGContextDrawImage绘制图片  这里设置不正确的话 会导致视频颠倒
        
        //    当通过CGContextDrawImage绘制图片到一个context中时,如果传入的是UIImage的CGImageRef,因为UIKit和CG坐标系y轴相反,所以图片绘制将会上下颠倒
        
        CGContextDrawImage(context,CGRectMake(0,0,CGImageGetWidth(image),CGImageGetHeight(image)), image);
        
        // 释放色彩空间
        
        CGColorSpaceRelease(rgbColorSpace);
        
        // 释放context
        
        CGContextRelease(context);
        
        // 解锁pixel buffer
        
        CVPixelBufferUnlockBaseAddress(pxbuffer,0);
        
        return pxbuffer;
    }
    
    5.视频播放按钮点击操作事件
    //视频播放按钮点击操作
    - (void)playAction {
        
        NSLog(@"************%@",self.theVideoPath);
        
        // 文件管理器
        NSFileManager *fileManager = [[NSFileManager alloc]init];
        
        if (![fileManager fileExistsAtPath:self.theVideoPath]) {
            self.ww_progressLbe.text = @"文件不存在";
            return;
        }
        
        NSURL *sourceMovieURL = [NSURL fileURLWithPath:self.theVideoPath];
        
        AVAsset *movieAsset = [AVURLAsset URLAssetWithURL:sourceMovieURL options:nil];
        
        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];
        
        AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
        
        AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
        
        playerLayer.frame = CGRectMake(0, WWScreamH * 0.25, WWScreamW, WWScreamH * 0.65);
        
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
        
        [self.view.layer addSublayer:playerLayer];
        
        [player play];
        
    }
    

    项目连接地址
    效果图如下
    视频布局

    WechatIMG43.jpeg
    视频播放 WechatIMG42.jpeg

    相关文章

      网友评论

      • 心语风尚:为什么转换 耗内存 1G
      • 华_Roger:大赞,请问大神,在视频的图片切换时能否加入其他效果?比如淡入淡出、百叶窗、平移等等等等的效果?

      本文标题:iOS多张图片合成视频

      本文链接:https://www.haomeiwen.com/subject/qjvdrxtx.html