iOS多張圖片合成視頻

引言:隨著多媒體的普及,越來越多的短視頻被人們所喜愛,接受,傳播,所以快速而有效的合成高質(zhì)量的視頻成為剛需。

本文參考Skylpy作者,在原有基礎(chǔ)上進(jìn)行擴(kuò)展,編碼整理。

1.因?yàn)樯婕暗揭曨l合成和播放,所以需求先引入一些和視頻相關(guān)的資源庫
#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;  //經(jīng)過壓縮的圖片
}

//視頻地址
@property(nonatomic,strong)NSString*theVideoPath;
//合成進(jìn)度
@property(nonatomic,strong)UILabel *ww_progressLbe;
2.定義一個(gè)方法用于視圖布局
- (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];
     
    //視頻合成播放進(jìn)度提示文本框
    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 = @"準(zhǔn)備就緒";
    lbe.font = [UIFont systemFontOfSize:12];
    self.ww_progressLbe = lbe;
    [self.view addSubview:lbe];
}
3.定義一個(gè)方法用于賦值數(shù)據(jù)
- (void)ww_setupInit {
    
    imageArray = [[NSMutableArray alloc]init];
    imageArr = [[NSMutableArray alloc]init];
    
    NSString *name = @"";
    UIImage *img = nil;
    
    //實(shí)先準(zhǔn)備21張圖片,命名為0.jpg至21.jpg
    for (int i = 0; i < 22; i++) {
        name = [NSString stringWithFormat:@"%d",i];
        img = [UIImage imageNamed:name];
        [imageArr addObject:img];
    }
   
    //對圖片進(jìn)行裁剪,方便合成等比例視頻
    for (int i = 0; i < imageArr.count; i++) {
        
        UIImage *imageNew = imageArr[i];
        
        //設(shè)置image的尺寸
        CGSize imgeSize = CGSizeMake(320, 480);
        
        //對圖片大小進(jìn)行壓縮--
        imageNew = [self imageWithImage:imageNew scaledToSize:imgeSize];
        
        [imageArray addObject:imageNew];
    }
}

對圖片進(jìn)行壓縮方法如下

-(UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize

{
    //    新創(chuàng)建的位圖上下文 newSize為其大小
    UIGraphicsBeginImageContext(newSize);
    //    對圖片進(jìn)行尺寸的改變
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    
    //    從當(dāng)前上下文中獲取一個(gè)UIImage對象  即獲取新的圖片對象
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    return newImage;
}
4.視頻合成按鈕點(diǎn)擊操作事件
//視頻合成按鈕點(diǎn)擊操作
- (void)testCompressionSession {
    
    //設(shè)置mov路徑
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
    
    NSString *moviePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov",@"test"]];
    
    self.theVideoPath=moviePath;
    
    //定義視頻的大小320 480 倍數(shù)
    CGSize size = CGSizeMake(320,480);
    
    NSError *error = nil;
    
    //    轉(zhuǎn)成UTF-8編碼
    unlink([moviePath UTF8String]);
    
    NSLog(@"path->%@",moviePath);
    
    //     iphone提供了AVFoundation庫來方便的操作多媒體設(shè)備,AVAssetWriter這個(gè)類可以方便的將圖像和音頻寫成一個(gè)完整的視頻文件
    
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc]initWithURL:[NSURL fileURLWithPath:moviePath]fileType:AVFileTypeQuickTimeMovie error:&error];
    
    NSParameterAssert(videoWriter);
    
    if(error) {
        NSLog(@"error =%@",[error localizedDescription]);
        return;
    }
    
    //mov的格式設(shè)置 編碼格式 寬度 高度
    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實(shí)例,
    //    可以使用分配像素緩沖區(qū)寫入輸出文件。使用提供的像素為緩沖池分配通常
    //    是更有效的比添加像素緩沖區(qū)分配使用一個(gè)單獨(dú)的池
    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];
    
    //合成多張圖片為一個(gè)視頻文件
    
    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:@"合成進(jìn)度:%@",progress];
                
            }];

            
            buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:[[imageArray objectAtIndex:idx]CGImage]size:size];
            
            if(buffer){
                
                //設(shè)置每秒鐘播放圖片的個(gè)數(shù)
                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();
    
    //    當(dāng)你調(diào)用這個(gè)函數(shù)的時(shí)候,Quartz創(chuàng)建一個(gè)位圖繪制環(huán)境,也就是位圖上下文。當(dāng)你向上下文中繪制信息時(shí),Quartz把你要繪制的信息作為位圖數(shù)據(jù)繪制到指定的內(nèi)存塊。一個(gè)新的位圖上下文的像素格式由三個(gè)參數(shù)決定:每個(gè)組件的位數(shù),顏色空間,alpha選項(xiàng)
    
    CGContextRef context = CGBitmapContextCreate(pxdata,size.width,size.height,8,4*size.width,rgbColorSpace,kCGImageAlphaPremultipliedFirst);
    
    NSParameterAssert(context);
    
    //使用CGContextDrawImage繪制圖片  這里設(shè)置不正確的話 會(huì)導(dǎo)致視頻顛倒
    
    //    當(dāng)通過CGContextDrawImage繪制圖片到一個(gè)context中時(shí),如果傳入的是UIImage的CGImageRef,因?yàn)閁IKit和CG坐標(biāo)系y軸相反,所以圖片繪制將會(huì)上下顛倒
    
    CGContextDrawImage(context,CGRectMake(0,0,CGImageGetWidth(image),CGImageGetHeight(image)), image);
    
    // 釋放色彩空間
    
    CGColorSpaceRelease(rgbColorSpace);
    
    // 釋放context
    
    CGContextRelease(context);
    
    // 解鎖pixel buffer
    
    CVPixelBufferUnlockBaseAddress(pxbuffer,0);
    
    return pxbuffer;
}
5.視頻播放按鈕點(diǎn)擊操作事件
//視頻播放按鈕點(diǎn)擊操作
- (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];
    
}

項(xiàng)目連接地址
效果圖如下
視頻布局

WechatIMG43.jpeg

視頻播放

WechatIMG42.jpeg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,165評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,435評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,943評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,175評(píng)論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,713評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,599評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,788評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,034評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評(píng)論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,408評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,747評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,599評(píng)論 25 707
  • 清晨 我又來到 小樹林 樹上知了聲聲鳴 我豎著耳朵聽 有渴望 有起伏 有不甘 有較勁 …… …… …… 一聲一...
    雪莉詩話閱讀 302評(píng)論 18 13
  • 在牛奶的標(biāo)準(zhǔn)生產(chǎn)過程中,脫脂其實(shí)是它們的必經(jīng)之路。是的,就算日后它們要成為的是全脂牛奶,也需要先脫脂。 新鮮牛奶中...
    小綠葉mj閱讀 6,209評(píng)論 0 0
  • 如果真的愛他 就給他自由空間 雖然不舍 盡管難過 但你知道嗎 抓的越緊 最后 失去的就越多 為他你也許付出過 但如...
    貓三角看世界閱讀 508評(píng)論 0 4
  • 坐在車上的時(shí)候想到了這個(gè)神奇的話題。 現(xiàn)在的90后、00后在信息的洪流中不斷做出選擇,導(dǎo)致每人間差異越來越巨大化。...
    柳大大一閱讀 462評(píng)論 0 1