Skip to content

GitLab

  • Menu
Projects Groups Snippets
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • V VOD_iOS_ShortVideo_SDK
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Monitor
    • Monitor
    • Incidents
  • Packages & Registries
    • Packages & Registries
    • Infrastructure Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • CCVideo
  • VOD_iOS_ShortVideo_SDK
  • Wiki
  • Home

Last edited by Hanruisong Oct 09, 2020
Page history

Home

1.概述

iOS短视频SDK是适用于iOS平台的短视频SDK。使用此SDK可以实现短视频相关功能。

1.1 功能特性

功能 描述
拍摄 支持短视频拍摄
摄像头切换 支持拍摄过程中切换摄像头
断点拍摄 支持拍摄过程中可暂停
美颜滤镜 支持拍摄加入美颜滤镜
视频时长剪辑 支持选取视频时长剪辑
视频区域剪辑 支持选取视频区域剪辑
视频倍速调整 支持选取视频倍速调整
多视频合成 支持多视频合成
视频水印 支持根据时段,位置添加视频水印
气泡文字 支持根据时段,位置添加气泡文字
音频插入 支持对视频插入背景音乐
视频大小修改 支持修改视频分辨率
图片合成视频 支持多图片合成视频,添加动效
视频特效 支持视频特效添加

1.2 阅读对象

本文档为技术文档,需要阅读者:

  • 具备基本的iOS开发能力

  • 准备接入CC视频的短视频SDK

2.开发准备

2.1 开发环境

  • Xcode:苹果官方IDE

  • iOS8+

2.2 SDK使用说明

首先,需要下载最新版本的SDK,下载地址为:VOD_iOS_ShortVideo_SDK,然后导入SDK包到工程,在相应地方引入头文件#import "DWShortSDK"

名称 描述
Demo 使用SDK的示例源码
doc 存放的是iOS短视频SDK开发指南html文档
include SDK开放的头文件及静态库

3.快速集成

注:快速集成主要提供的是拍摄功能

3.1 拍摄快速集成

3.1.1 SDK添加到项目后,在info.plist文件设置麦克风 相机 相册 图片权限

<!-- 相册 -->   
<key>NSPhotoLibraryUsageDescription</key>   
<string>App需要您的同意,才能访问相册</string>   
<!-- 相机 -->   
<key>NSCameraUsageDescription</key>   
<string>App需要您的同意,才能访问相机</string>   
<!-- 麦克风 -->   
<key>NSMicrophoneUsageDescription</key>   
<string>App需要您的同意,才能访问麦克风</string>
<!-- 图片 --> 
<key>NSPhotoLibraryAddUsageDescription</key>
<string>App需要您的同意,才能保存图片到您的相册</string>  

3.1.2 基于GPUImage 建议用cocoapods导入 也可手动导入 确保工程中只导入一次GPUImage

3.1.3 GPUImage引入后 修改以下部分:

  1.GPUImageMovieWriter.h文件中添加isNeedBreakAudioWhiter属性
 
    @property (nonatomic, assign) BOOL isNeedBreakAudioWhiter;
 
  2.GPUImageMovieWriter.m文件中第377行代码修改如下:
    if (CMTIME_IS_INVALID(startTime))
     {
       if (_isNeedBreakAudioWhiter) {
 
 
      }else{
 
      runSynchronouslyOnContextQueue(_movieWriterContext, ^{
      if ((audioInputReadyCallback == NULL) && (assetWriter.status != AVAssetWriterStatusWriting))
    {
       [assetWriter startWriting];
     }
       [assetWriter startSessionAtSourceTime:currentSampleTime];
       startTime = currentSampleTime;
 
     });
         
         }
 
    }
 
 3.GPUImageMovieWriter初始化时设置 isNeedBreakAudioWhiter =YES;
 具体详情参见demo

3.1.3 初始化相机

    //录制相关
    self.videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720
                                                      cameraPosition:AVCaptureDevicePositionBack];
    
    
    if ([self.videoCamera.inputCamera lockForConfiguration:nil]) {
        //自动对焦
        if ([self.videoCamera.inputCamera isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
            [self.videoCamera.inputCamera setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
        }
        //自动曝光
        if ([self.videoCamera.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
            [self.videoCamera.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
        }
        //自动白平衡
        if ([self.videoCamera.inputCamera isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) {
            [self.videoCamera.inputCamera setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
        }
        
        [self.videoCamera.inputCamera unlockForConfiguration];
    }
    
    
    //输出方向为竖屏
    self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
    //防止允许声音通过的情况下,避免录制第一帧黑屏闪屏
    [self.videoCamera addAudioInputsAndOutputs];
    self.videoCamera.horizontallyMirrorFrontFacingCamera = YES;
    self.videoCamera.horizontallyMirrorRearFacingCamera = NO;
     
    //相机开始运行
    [self.videoCamera startCameraCapture];

3.1.4 初始化moviewriter

    //苹果默认是MOV格式
    NSString *path =[self getVideoSaveFilePathString:@".MOV" addPathArray:YES];
    unlink([path UTF8String]);
    self.videoURL = [NSURL fileURLWithPath:path];
    
    //写入
    CGSize size = CGSizeZero;
    if (self.scale == 0) {
        //9:16
        size = CGSizeMake(720.0, 1280.0);
    }else if (self.scale == 1){
        //3:4
        size = CGSizeMake(720.0, 960.0);
    }else{
        //1:1
        size = CGSizeMake(720.0, 720.0);
    }
    
    self.movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:self.videoURL size:size];
    
    //设置为liveVideo
    self.movieWriter.isNeedBreakAudioWhiter =YES;
    self.movieWriter.encodingLiveVideo = YES;
    self.movieWriter.shouldPassthroughAudio =YES;
 
    //根据实际需求,添加滤镜链
     if (self.filter) {
        if (self.isBeautyFilter) {
            if (self.isBilateralExist) {
                [self.bilateralFilter addTarget:self.movieWriter];
            }else{
                [self.brightnessFilter addTarget:self.movieWriter];
            }
        }else{
            [self.filter addTarget:self.movieWriter];
        }
    }else{
        if (self.isBeautyFilter) {
            if (self.isBilateralExist) {
                [self.bilateralFilter addTarget:self.movieWriter];
            }else{
                [self.brightnessFilter addTarget:self.movieWriter];
            }
        }else{
            [self.videoCamera addTarget:self.movieWriter];
        }
    }
    
    //设置声音
    self.videoCamera.audioEncodingTarget = self.movieWriter;

4.功能使用

4.1 删除功能具体使用

    BOOL isSuccess = [DWShortTool dw_deleteFileWithFilePath:filePath];
    
    if (self.pathArray.count == 0) {
        return YES;
    }
    
    if (isSuccess) {

        [self.pathArray removeLastObject];
        
        UIView * view = [self.viewArray lastObject];
        [view removeFromSuperview];
        [self.viewArray removeLastObject];
                
        //要减去相应的录制时间
        double recordTime =[[self.secondsArray lastObject] doubleValue];
        self.totalTime -= recordTime;
        [self.secondsArray removeLastObject];
        [self.bottomView setRecordTime:self.totalTime];
        
        NSLog(@"余下录制时间%f__%f",self.totalTime,recordTime);
    }

4.2 相机功能具体使用

			  //闪光灯的使用
        //前置摄像头不打开闪光灯
        if (self.videoCamera.inputCamera.position == AVCaptureDevicePositionFront) {
            return;
        }
  
        button.selected = !button.selected;
        if (self.videoCamera.inputCamera.position == AVCaptureDevicePositionBack) {
            if (self.videoCamera.inputCamera.torchMode ==AVCaptureTorchModeOn) {
                [self.videoCamera.inputCamera lockForConfiguration:nil];
                [self.videoCamera.inputCamera setTorchMode:AVCaptureTorchModeOff];
                [self.videoCamera.inputCamera setFlashMode:AVCaptureFlashModeOff];
                [self.videoCamera.inputCamera unlockForConfiguration];
            }else{
                [self.videoCamera.inputCamera lockForConfiguration:nil];
                [self.videoCamera.inputCamera setTorchMode:AVCaptureTorchModeOn];
                [self.videoCamera.inputCamera setFlashMode:AVCaptureFlashModeOn];
                [self.videoCamera.inputCamera unlockForConfiguration];
            }
        }
        
        //前后摄像头切换
        [self.videoCamera rotateCamera];

4.3 美颜滤镜功能具体使用

    //再添加新的滤镜效果前,首先移除响应链上的滤镜
    [self removeAllTarget];
    
    if (type == 0) {
        self.filter = nil;
    }
    
    //根据滤镜效果,添加合适的滤镜。
    //这里的滤镜效果仅供参考,更多效果请详见GPUImage功能介绍。
    if (type == 1) {
        GPUImageSaturationFilter * saturationFilter = [[GPUImageSaturationFilter alloc]init];
        saturationFilter.saturation = 2;
        self.filter = saturationFilter;
    }
    
    if (type == 2) {
        GPUImageSaturationFilter * saturationFilter = [[GPUImageSaturationFilter alloc]init];
        saturationFilter.saturation = 0.7;
        self.filter = saturationFilter;
    }
    
    if (type == 3) {
        GPUImageWhiteBalanceFilter * whiteBalanceFilter = [[GPUImageWhiteBalanceFilter alloc]init];
        whiteBalanceFilter.temperature = 4500;
        self.filter = whiteBalanceFilter;
    }
    
    if (type == 4) {
        GPUImageSepiaFilter * sepiaFilter = [[GPUImageSepiaFilter alloc]init];
        self.filter = sepiaFilter;
    }
    
    if (type == 5) {
        GPUImageExposureFilter * exposureFilter = [[GPUImageExposureFilter alloc]init];
        exposureFilter.exposure = 0.3;
        self.filter = exposureFilter;
    }
    
    //添加新的滤镜响应链
    if (self.filter) {
        if (self.isBeautyFilter) {
            if (self.isBilateralExist) {
                [self.videoCamera addTarget:self.filter];
                [self.filter addTarget:self.brightnessFilter];
                [self.brightnessFilter addTarget:self.bilateralFilter];
                [self.bilateralFilter addTarget:self.filterView];
            }else{
                [self.videoCamera addTarget:self.filter];
                [self.filter addTarget:self.brightnessFilter];
                [self.brightnessFilter addTarget:self.filterView];
            }
        }else{
            [self.videoCamera addTarget:self.filter];
            [self.filter addTarget:self.filterView];
        }
    }else{
        if (self.isBeautyFilter) {
            if (self.isBilateralExist) {
                [self.videoCamera addTarget:self.brightnessFilter];
                [self.brightnessFilter addTarget:self.bilateralFilter];
                [self.bilateralFilter addTarget:self.filterView];
            }else{
                [self.videoCamera addTarget:self.brightnessFilter];
                [self.brightnessFilter addTarget:self.filterView];
            }
        }else{
            [self.videoCamera addTarget:self.filterView];
        }
    }
 }

4.4 暂停or开始录制

    //开始录制
    //UI修改
    [self hiddenAllView:YES];
    [self.bottomView setStyle:2];
    
    if (self.isDelay) {
        //__weak typeof(self) weakSelf = self;
        //开始延迟计时
        DWDelayView * delayView = [[DWDelayView alloc]init];
        [delayView beginAnimation];
        
        delayView.finish = ^{
               //进度条
               [self initProgressView];
               self.seconds = 0;
               
               [self gcdTimer];

               //开始录制
               [self initMovieWriter];
               
               [self.movieWriter startRecording];
        };
        return;
    }
        
    //进度条
    [self initProgressView];
    self.seconds = 0;
    
    //启动计时器
    [self gcdTimer];

    //开始录制
    [self initMovieWriter];
    [self.movieWriter startRecording];
    
    //暂停录制 
    //UI修改
    [self hiddenAllView:NO];

    [self.bottomView setStyle:3];
    
    //移除定时器
    [self removeTimer];
    //暂停写入
    [self.movieWriter finishRecording];
    self.videoCamera.audioEncodingTarget = nil;
    
    [self.secondsArray addObject:[NSString stringWithFormat:@"%f",self.seconds]];

    //保存视频到本地
    [self savePhotosAlbum:self.videoURL];
   

4.5 断点拍摄功能具体使用

断点拍摄后的几个小视频可以合成为一个视频,DWShortTool提供了接口(4.10.4)用于此功能。

	  //延迟执行,等待视频写入完成。
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        //合成视频输出路径
        NSString * path = [self getVideoSaveFilePathString:@".MOV" addPathArray:NO];

        self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
        self.hud.removeFromSuperViewOnHide = YES;
        self.hud.label.text = @"视频生成中";

        [DWShortTool dw_compositeAndExportVideos:self.pathArray withVideoSize:CGSizeZero outPath:path outputFileType:AVFileTypeQuickTimeMovie presetName:AVAssetExportPresetHighestQuality didComplete:^(NSError *error, NSURL *compressionFileURL) {
            
            [self.hud hideAnimated:YES];
            
            if (!error) {
                                
                DWShortVideoEditViewController * shortVideoEditVC = [[DWShortVideoEditViewController alloc]init];
                shortVideoEditVC.videoURL = [NSURL fileURLWithPath:path];
                [self presentViewController:shortVideoEditVC animated:YES completion:nil];
            }else{
                [@"合成失败,请重试" showAlert];
            }
        }];
    });

4.6 视频剪辑功能的使用

通过设置裁剪时间,裁剪范围,裁剪倍速来对视频进行剪辑处理。DWShortTool提供了接口(4.10.2)用于此功能。

NSString *videoPath = [self createFilePath];
    
    MBProgressHUD * hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.removeFromSuperViewOnHide = YES;
    
    [DWShortTool dw_videoSizeAndTimeCropAndExportVideo:self.videoURL.absoluteString
                                                  size:self.cropView.scaleFrame.size
                                                 point:self.cropView.scaleFrame.origin
                                                 range:CMTimeRangeMake(self.cropView.start, self.cropView.duration)
                                             videoRate:self.cropView.speed
                                           withOutPath:videoPath
                                        outputFileType:AVFileTypeQuickTimeMovie
                                            presetName:AVAssetExportPresetHighestQuality
                                           didComplete:^(NSError *error, NSURL *compressionFileURL) {
        
        [hud hideAnimated:YES];
                
        if (error) {
            [error.localizedDescription showAlert];
            return;
        }
        
        //剪辑完成,跳转编辑页面
        DWShortVideoEditViewController * shortVideoEditVC = [[DWShortVideoEditViewController alloc]init];
        shortVideoEditVC.videoURL = compressionFileURL;
        [self presentViewController:shortVideoEditVC animated:YES completion:nil];
    }];

4.7 插入背景音乐功能的使用

可以设置新的背景音乐插入到原视频中。DWShortTool提供了接口(4.10.5)用于此功能。

	    //判断是否选择了音频
    BOOL select = NO;
    for (NSDictionary * dict in self.musicListArray) {
        if ([[dict objectForKey:@"isSelect"] boolValue]) {
            select = YES;
            break;
        }
    }
    
    if (!select) {
        self.musicDict = nil;
        [self addStickerWithVideoUrl:videoUrl];
        return;
    }
    
    //插入音频
    NSString * outPath = [self createFilePath];
    //        NSURL * outPathUrl = [NSURL fileURLWithPath:outPath];
    
    [DWShortTool dw_insertAudioAndExportVideo:videoUrl.absoluteString
                                withAudioPath:[self.musicDict objectForKey:@"audioPath"]
                               originalVolume:[[self.musicDict objectForKey:@"originalVolume"] floatValue]
                                 insertVolume:[[self.musicDict objectForKey:@"insertVolume"] floatValue]
                                    timeRange:CMTimeRangeMake([[self.musicDict objectForKey:@"start"] CMTimeValue], [[self.musicDict objectForKey:@"duration"] CMTimeValue])
                                      outPath:outPath
                               outputFileType:OUTPUTFILETYPE
                                   presetName:PRESETNAME
                                  didComplete:^(NSError *error, NSURL *compressionFileURL) {
        
        
        if (error) {
            
            [self.editAndUploadVC endEditWithSuccess:NO];
            
            [error.localizedDescription showAlert];
            return;
        }
        
    }];

4.8 贴纸功能的使用

通过设置贴纸图片,贴纸位置(注意,贴纸point,size的参数为百分比),以及贴纸出现的时间范围为原视频增加视频贴纸。DWShortTool提供了接口(4.10.3)用于此功能。

if (!self.canEdit) {
        return;
    }
    
    //判断是否添加贴纸
    if (self.stickerArray.count == 0) {
        [self addBubbleVideoUrl:videoUrl];
        return;
    }
    
    NSString * outPath = [self createFilePath];
    
    NSArray * images = @[@"icon_sticker_1.png",@"icon_sticker_2.png",@"icon_sticker_3.png",@"icon_sticker_4.png",@"icon_sticker_5.png"];
    NSMutableArray * stickerImages = [NSMutableArray array];
    NSMutableArray * stickerImagePoints = [NSMutableArray array];
    NSMutableArray * stickerImageSizes = [NSMutableArray array];
    NSMutableArray * rotateAngles = [NSMutableArray array];
    NSMutableArray * timeRanges = [NSMutableArray array];
    
    for (NSDictionary * dict in self.stickerArray) {
        UIImage * stickerImage = [UIImage imageNamed:[images objectAtIndex:[[dict objectForKey:@"index"] integerValue]]];
        LINConversionView * conversionView = [dict objectForKey:@"object"];
        CGRect frame = conversionView.frame;
        CGAffineTransform transform = conversionView.transform;

        CGFloat rotate = [self getAngleFromAffineTransform:transform];
        CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];
        //计算偏移量
        CGFloat offset = (conversionView.frame.size.width - sizeLength) / 2.0;
        CGPoint offsetPoint = CGPointMake(frame.origin.x + offset, frame.origin.y + offset);
        //百分比
        CGPoint stickerImagePoint = CGPointMake((offsetPoint.x - self.videoBackground.origin.x) / self.videoBackground.size.width, (offsetPoint.y - self.videoBackground.origin.y) / self.videoBackground.size.height);
        CGSize stickerImageSize = CGSizeMake(sizeLength / self.videoBackground.size.width, sizeLength / self.videoBackground.size.height);

        CMTime start = [[dict objectForKey:@"start"] CMTimeValue];
        CMTime duration = [[dict objectForKey:@"duration"] CMTimeValue];
        //如果需要整个视频都添加贴纸,传kCMTimeZero即可。
        if (CMTimeCompare(duration, self.videoDuration) == 0) {
            duration = kCMTimeZero;
        }
        CMTimeRange timeRange = CMTimeRangeMake(start, duration);
        
        [stickerImages addObject:stickerImage];
        [stickerImagePoints addObject:[NSValue valueWithCGPoint:stickerImagePoint]];
        [stickerImageSizes addObject:[NSValue valueWithCGSize:stickerImageSize]];
        [rotateAngles addObject:[NSNumber numberWithFloat:rotate]];
        [timeRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
       
    }

    [DWShortTool dw_addStickerAndExportVideo:videoUrl.absoluteString
                           withStickerImages:stickerImages
                          stickerImagePoints:stickerImagePoints
                           stickerImageSizes:stickerImageSizes
                                rotateAngles:rotateAngles
                                  timeRanges:timeRanges
                                     outPath:outPath
                              outputFileType:OUTPUTFILETYPE
                                  presetName:PRESETNAME
                                 didComplete:^(NSError *error, NSURL *compressionFileURL) {
        
        if (error) {
            [self.editAndUploadVC endEditWithSuccess:NO];
            
            return;
        }
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self addBubbleVideoUrl:compressionFileURL];
        });
        
    }];

4.9 气泡文字功能的使用

添加逻辑同4.8,贴纸图片需要通过背景图与文字生成。

 if (!self.canEdit) {
        return;
    }
    
    if (self.bubbleArray.count == 0) {
        [self.editAndUploadVC endEditWithSuccess:YES];
        
        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library writeVideoAtPathToSavedPhotosAlbum:videoUrl completionBlock:^(NSURL *assetURL, NSError *error)
         {
            
        }];
        return;
    }
    
    NSString * outPath = [self createFilePath];

    NSMutableArray * bubbleImages = [NSMutableArray array];
    NSMutableArray * bubbleImagePoints = [NSMutableArray array];
    NSMutableArray * bubbleImageSizes = [NSMutableArray array];
    NSMutableArray * rotateAngles = [NSMutableArray array];
    NSMutableArray * timeRanges = [NSMutableArray array];
    for (NSDictionary * dict in self.bubbleArray) {
        LINConversionView * conversionView = [dict objectForKey:@"object"];
        CGRect frame = conversionView.frame;
        CGAffineTransform transform = conversionView.transform;
        
        CGFloat rotate = [self getAngleFromAffineTransform:transform];
        CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];

        //计算偏移量
        CGFloat offset = (conversionView.frame.size.width - sizeLength) / 2.0;
        CGPoint offsetPoint = CGPointMake(frame.origin.x + offset, frame.origin.y + offset);
        
        //获取贴纸所占视频区域的百分比
        CGPoint bubbleImagePoint = CGPointMake((offsetPoint.x - self.videoBackground.origin.x) / self.videoBackground.size.width, (offsetPoint.y - self.videoBackground.origin.y) / self.videoBackground.size.height);

        CGSize bubbleImageSize = CGSizeMake(sizeLength / self.videoBackground.size.width, sizeLength / self.videoBackground.size.height);
        
        CMTime start = [[dict objectForKey:@"start"] CMTimeValue];
        CMTime duration = [[dict objectForKey:@"duration"] CMTimeValue];
        //如果需要整个视频都添加贴纸,传kCMTimeZero即可。
        if (CMTimeCompare(duration, self.videoDuration) == 0) {
            duration = kCMTimeZero;
        }
        CMTimeRange timeRange = CMTimeRangeMake(start, duration);
        
        //生成贴纸图片
        [bubbleImages addObject:[self bubbleImageWithBubbleDict:dict]];
        [bubbleImagePoints addObject:[NSValue valueWithCGPoint:bubbleImagePoint]];
        [bubbleImageSizes addObject:[NSValue valueWithCGSize:bubbleImageSize]];
        [rotateAngles addObject:[NSNumber numberWithFloat:rotate]];
        [timeRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
    }

    [DWShortTool dw_addStickerAndExportVideo:videoUrl.absoluteString
                           withStickerImages:bubbleImages
                          stickerImagePoints:bubbleImagePoints
                           stickerImageSizes:bubbleImageSizes
                                rotateAngles:rotateAngles
     
                                  timeRanges:timeRanges
                                     outPath:outPath
                              outputFileType:OUTPUTFILETYPE
                                  presetName:PRESETNAME
                                 didComplete:^(NSError *error, NSURL *compressionFileURL) {
        if (error) {
            [self.editAndUploadVC endEditWithSuccess:NO];
            return;
        }
        [self.editAndUploadVC endEditWithSuccess:YES];
        
        //保存视频到本地相册
        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library writeVideoAtPathToSavedPhotosAlbum:compressionFileURL completionBlock:^(NSURL *assetURL, NSError *error)
         {
            
        }];
    }];
    
//生成气泡文字图片
-(UIImage *)bubbleImageWithBubbleDict:(NSDictionary *)dict
{
    NSArray * images = @[@"icon_bubble_1.png",@"icon_bubble_2.png",@"icon_bubble_3.png",@"icon_bubble_4.png",@"icon_bubble_5.png"];
    //总体生成图片
    UIImage * bubbleImage = [UIImage imageNamed:[images objectAtIndex:[[dict objectForKey:@"index"] integerValue]]];
    LINConversionView * conversionView = [dict objectForKey:@"object"];
    DWBubbleInputView * inputView = (DWBubbleInputView *)conversionView.contentView;
    
    UIImageView * bubbleImageView = [[UIImageView alloc]init];
    
    CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];
    bubbleImageView.frame = CGRectMake(0, 0, sizeLength, sizeLength);
    bubbleImageView.image = bubbleImage;

    UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(bubbleImageView.frame.size.width * 32 / BUBBLEWIDTH, bubbleImageView.frame.size.height * 102 / BUBBLEWIDTH, bubbleImageView.frame.size.width - (bubbleImageView.frame.size.width * 32 / BUBBLEWIDTH) * 2, bubbleImageView.frame.size.height * 49 / BUBBLEWIDTH)];
    label.text = inputView.placeholderLabel.text;
    label.font = inputView.placeholderLabel.font;
    label.textColor = inputView.placeholderLabel.textColor;
    label.textAlignment = inputView.placeholderLabel.textAlignment;
    label.numberOfLines = 2;
    label.adjustsFontSizeToFitWidth = YES;
    [bubbleImageView addSubview:label];
    
    UIGraphicsBeginImageContextWithOptions(bubbleImageView.bounds.size, NO, 0);
    
    if ([bubbleImageView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
        
        [bubbleImageView drawViewHierarchyInRect:bubbleImageView.bounds afterScreenUpdates:YES];
    }else{
        [bubbleImageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    }
    
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

4.10 工具类DWShortTool接口功能

4.10.1 视频压缩

/// 压缩视频
/// @param videoPath 视频路径
/// @param outPath 输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后回调
+ (void)dw_compressionAndExportVideo:(NSString *)videoPath
                         withOutPath:(NSString *)outPath
                      outputFileType:(NSString *)outputFileType
                          presetName:(NSString *)presetName
                         didComplete:(void(^)(NSError *error,NSURL *compressionFileURL) )completeBlock;

4.10.2 视频剪辑

4.10.2.1 视频时长剪辑 视频区域不变

/// 视频时长剪辑 视频区域不变
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param timeRange 截取视频的时间范围
/// @param completeBlock 完成后回调
+ (void)dw_videoTimeCropAndExportVideo:(NSString *)videoPath
                           withOutPath:(NSString *)outPath
                        outputFileType:(NSString *)outputFileType
                            presetName:(NSString *)presetName
                                 range:(CMTimeRange)timeRange
                           didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.2.2 视频区域剪裁 视频时长不变

/// 视频区域剪裁 视频时长不变
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param videoSize 剪裁区域
/// @param videoPoint 剪裁起点
/// @param shouldScale 是否拉伸 YES拉伸 NO不拉伸 剪裁黑背景
/// @param completeBlock 剪裁完成后的回调
+ (void)dw_videoSizeCropAndExportVideo:(NSString *)videoPath
                           withOutPath:(NSString *)outPath
                        outputFileType:(NSString *)outputFileType
                            presetName:(NSString *)presetName
                                  size:(CGSize)videoSize
                                 point:(CGPoint)videoPoint
                           shouldScale:(BOOL)shouldScale
                           didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.2.3 视频区域兼时长剪裁

/// 视频区域兼时长剪裁
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param videoSize 剪裁区域
/// @param videoPoint 剪裁起点
/// @param shouldScale 是否拉伸 YES拉伸 NO不拉伸 剪裁黑背景
/// @param timeRange 截取视频的时间范围
/// @param completeBlock 剪裁完成后的回调
+ (void)dw_videoSizeAndTimeCropAndExportVideo:(NSString *)videoPath
                                  withOutPath:(NSString *)outPath
                               outputFileType:(NSString *)outputFileType
                                   presetName:(NSString *)presetName
                                         size:(CGSize)videoSize
                                        point:(CGPoint)videoPoint
                                  shouldScale:(BOOL)shouldScale
                                        range:(CMTimeRange)timeRange
                                  didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.2.4 视频区域,时长,倍速剪裁

/// 视频区域,时长,倍速剪裁
/// @param videoPath 视频路径
/// @param videoSize 剪裁区域,CGSizeZero不裁剪
/// @param videoPoint 剪裁起点
/// @param timeRange 截取视频的时间范围,kCMTimeRangeZero,不裁剪时长
/// @param videoRate 截取视频倍速
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_videoSizeAndTimeCropAndExportVideo:(NSString *)videoPath
                                         size:(CGSize)videoSize
                                        point:(CGPoint)videoPoint
                                        range:(CMTimeRange)timeRange
                                    videoRate:(CGFloat)videoRate
                                  withOutPath:(NSString *)outPath
                               outputFileType:(NSString *)outputFileType
                                   presetName:(NSString *)presetName
                                  didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.3 新增视频水印

4.10.3.1 添加单个视频水印

/// 添加单个视频水印
/// @param videoPath 视频路径
/// @param stickerImage 水印图片
/// @param stickerImagePoint 水印位置,point为水印所占视频位置的百分比。eg:CGPointMake(0.1, 0.1)
/// @param stickerImageSize 水印大小,为水印所占视频大小的百分比。eg:CGSizeMake(0.4, 0.4)
/// @param rotateAngle 水印旋转弧度。eg:M_PI_2
/// @param timeRange 添加水印的时间范围,kCMTimeRangeZero时,全部时长都添加水印
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_addStickerAndExportVideo:(NSString *)videoPath
                   withStickerImage:(UIImage *)stickerImage
                  stickerImagePoint:(CGPoint)stickerImagePoint
                   stickerImageSize:(CGSize)stickerImageSize
                        rotateAngle:(CGFloat)rotateAngle
                          timeRange:(CMTimeRange)timeRange
                            outPath:(NSString *)outPath
                     outputFileType:(NSString *)outputFileType
                         presetName:(NSString *)presetName
                        didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.3.2 批量添加视频水印

/// 批量添加视频水印
/// @param videoPath 视频路径
/// @param stickerImages 水印图片数组
/// @param stickerImagePoints 水印位置数组,points为水印所占视频位置的百分比数组。eg:@[[NSValue valueWithCGPoint:CGPointMake(0.1, 0.1)]]
/// @param stickerImageSizes 水印大小数组,sizes为水印所占视频大小的百分比数组。eg:@[[NSValue valueWithCGPoint:CGSizeMake(0.4, 0.4)]]
/// @param rotateAngles 水印旋转弧度数据。eg:[NSNumber numberWithFloat:M_PI_2]
/// @param timeRanges 添加水印的时间范围数组,kCMTimeRangeZero时,全部时长都添加水印
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_addStickerAndExportVideo:(NSString *)videoPath
                  withStickerImages:(NSArray <UIImage *>*)stickerImages
                 stickerImagePoints:(NSArray <NSValue *> *)stickerImagePoints
                  stickerImageSizes:(NSArray <NSValue *> *)stickerImageSizes
                        rotateAngles:(NSArray <NSNumber *> *)rotateAngles
                         timeRanges:(NSArray <NSValue *> *)timeRanges
                            outPath:(NSString *)outPath
                     outputFileType:(NSString *)outputFileType
                         presetName:(NSString *)presetName
                        didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.4 多视频合成

/// 多视频合成
/// @param videosPath 视频路径数组
/// @param videoSize 合成视频尺寸,不设置(CGRectZero)默认取第一个视频大小为基准
/// @param outPath 输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+(void)dw_compositeAndExportVideos:(NSArray <NSString *>*)videosPath
                         withVideoSize:(CGSize)videoSize
                           outPath:(NSString *)outPath
                    outputFileType:(NSString *)outputFileType
                        presetName:(NSString *)presetName
                       didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.5 插入音频

/// 插入音频
/// @param videoPath 视频路径
/// @param audioPath 待插入音频路径
/// @param originalVolume 原音频音量
/// @param insertVolume 插入音频音量
/// @param timeRange 插入音频范围,kCMTimeRangeZero时,从0开始添加
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+(void)dw_insertAudioAndExportVideo:(NSString *)videoPath
                      withAudioPath:(NSString *)audioPath
                     originalVolume:(CGFloat)originalVolume
                       insertVolume:(CGFloat)insertVolume
                          timeRange:(CMTimeRange)timeRange
                            outPath:(NSString *)outPath
                     outputFileType:(NSString *)outputFileType
                         presetName:(NSString *)presetName
                        didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.6 修改视频分辨率

/// 修改视频分辨率
/// @param videoPath 视频路径
/// @param videoSize 要输出的视频分辨率
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_videoSizeChangeRenderAndExportVideo:(NSString *)videoPath
                                     videoSize:(CGSize)videoSize
                                   withOutPath:(NSString *)outPath
                                outputFileType:(NSString *)outputFileType
                                    presetName:(NSString *)presetName
                                   didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;

4.10.7 其他开放接口

/// 取得缩略图
/// @param videoPath 文件路径
/// @param time 第几秒的缩略图
+(UIImage *)dw_getThumbnailImage:(NSString *)videoPath time:(NSTimeInterval)time;

/// 十六进制色彩
/// @param string 色值
+(UIColor *)dw_colorWithHexString:(NSString *)string;

/// 删除文件
/// @param filePath 文件路径 并返回是否成功
+(BOOL)dw_deleteFileWithFilePath:(NSString *)filePath;

/// 获取文件大小
/// @param filePath 文件路径
+(CGFloat)dw_fileSizeAtPath:(NSString*)filePath;

4.11 图片合成视频功能的使用

通过多张图片及图片特效,转场特效生成一段视频。使用步骤如下:

4.11.1 通过选取的图片,图片特效,转场特效等参数,生成DWPictureNodeModel对象

//图片特效通过DWPictureAnimation类生成
/// 返回图片特效动画数据
/// @param style 特效样式
/// @param pictureDuration 图片特效持续时间
/// @param frontTransitionDuration 转场动画持续时间
/// @param videoSize 合成视频的大小,左滑,右滑特效需要。
/// @param isLast 是否是最后一张图片
+(NSArray *)pictureAnimationCreateWithStyle:(DWPictureAnimationStyle)style
                            PictureDuration:(CGFloat)pictureDuration
                    FrontTransitionDuration:(CGFloat)frontTransitionDuration
                                  VideoSize:(CGSize)videoSize
                                     isLast:(BOOL)isLast;
                                     
//转场特效通过DWTransitionAnimation类生成
/// 返回转场动画特效
/// @param style 转场动画样式
/// @param beginTime 特效持续时间
/// @param duration 转场动画持续时间
/// @param videoSize 合成视频的大小
/// @param isFront 转场特效是否在图片之前
+(NSArray *)transitionAnimationCreateWithStyle:(DWTransitionAnimationStyle)style
                                     BeginTime:(CGFloat)beginTime
                                      Duration:(CGFloat)duration
                                     VideoSize:(CGSize)videoSize
                                       isFront:(BOOL)isFront;

//生成圆形动画
+(NSArray *)circleMaskAnimationCreateWithDuration:(CGFloat)duration
                                        VideoSize:(CGSize)videoSize;


//DWPictureNodeModel节点对象的创建
DWPictureNodeModel * pictureNodeModel = [[DWPictureNodeModel alloc]init];
pictureNodeModel.image = ...; //图片,非空
pictureNodeModel.beginTime = ...; //图片特效起始时间
pictureNodeModel.duration = ...; //图片特效持续时间
pictureNodeModel.animations = ...; //图片特效及转场特效,DWPictureAnimation,DWTransitionAnimation 生成的特效数组集合。
pictureNodeModel.maskLayer = ...; //图层layer的mask图层,用于辅助完成动画效果
pictureNodeModel.maskAnimationBeginTime = ...; //mask图层特效开始时间
pictureNodeModel.maskAnimationDuration = ...; //mask图层特效持续时间
pictureNodeModel.maskAnimations = ...; //mask图层的特效
pictureNodeModel.isCover = ...; //图层layer的添加顺序

详细实现逻辑,可参考DWShortPictureCompositeViewController中-(NSArray *)resetPictureNodeAniationsWithSize:(CGSize)aniationsSize AndCreateVideo:(BOOL)isCreateVideo;方法的实现逻辑。

###4.11.2 创建DWPictureCompositeInstrument对象,设置适当的参数,生成视频

//根据DWPictureNodeModel节点对象,初始化视频生成器
DWPictureCompositeInstrument * instrument = [[DWPictureCompositeInstrument alloc]init];
instrument.videoSize = ...; //视频分辨率,非空
instrument.videoBackgroundColor = ...; //合成视频底色,默认纯黑
instrument.outPath = ...; 视频导出路径,非空
instrument.pictureNodeModels = ...; //步骤1所生成的节点对象数组
instrument.videoDuration = ...;//视频总时间    
//开始生成视频
[instrument startComposite];
//成功or失败回调
instrument.complete = ^(NSError *error, NSURL *completeFileURL) {
        if (error) {
           //视频生成失败处理及原因
           return;
        }
        //视频生成成功
        ...
    };
    
详细实现逻辑,可参考DWShortPictureCompositeViewController中-(void)nextButtonAction方法。
    

4.12 视频特效功能的使用

视频特效功能是在GPUImage的基础上,通过自定义滤镜来实现各种视频特效。添加视频特效的关键代码如下,其他页面逻辑可参考demo中的实现。

//查询最后的非视频特效滤镜
-(GPUImageOutput *)searchEffectOutput:(GPUImageOutput *)output
{
    //视频特效滤镜添加顺序
    //DWGPUImageShakeFilter
    //DWGPUImageFlashFilter
    //DWGPUImageSoulOutFilter
    //DWGPUImageVertigoFilter
    //DWGPUImageScaleFilter
    GPUImageOutput * lastoutput = nil;
    
    for (id subTarget in output.targets) {
        if (subTarget == self.filterView) {
            [self.movieFile removeAllTargets];
            lastoutput = output;
            break;
        }
        GPUImageFilter * subFilter = (GPUImageFilter *)subTarget;

        if (subFilter == self.effectShowFilter) {
            lastoutput = output;
            break;
        }
        
        if ([subFilter isKindOfClass:[DWGPUImageShakeFilter class]]) {
            //跳出递归
            [subFilter removeAllTargets];
            lastoutput = output;
        }else{
            lastoutput = [self searchEffectOutput:subFilter];
        }
    }
    
    return lastoutput;
}


//新增视频特效滤镜
-(GPUImageFilter *)addEffectFiltersWithLastOutput:(GPUImageOutput *)output
{
    //视频特效滤镜添加顺序
    //DWGPUImageShakeFilter 
    //DWGPUImageFlashFilter 
    //DWGPUImageSoulOutFilter 
    //DWGPUImageVertigoFilter 
    //DWGPUImageScaleFilter 
    NSMutableArray * shakeFilterArray = [NSMutableArray array];
    NSMutableArray * flashFilterArray = [NSMutableArray array];
    NSMutableArray * soulOutFilterArray = [NSMutableArray array];
    NSMutableArray * vertigoFilterArray = [NSMutableArray array];
    NSMutableArray * scaleFilterArray = [NSMutableArray array];

    [self.effectsArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSInteger style = [[obj objectForKey:@"style"] integerValue];
        switch (style) {
            case 0:{
                if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
                    id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
                    if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
                        [shakeFilterArray addObject:effectLoadTime];
                    }else{
                        [shakeFilterArray addObjectsFromArray:effectLoadTime];
                    }
                }
            }
                break;
            case 1:{
                if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
                    id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
                    if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
                        [flashFilterArray addObject:effectLoadTime];
                    }else{
                        [flashFilterArray addObjectsFromArray:effectLoadTime];
                    }
                }
     
            }
                break;
            case 2:{
                if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
                    id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
                    if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
                        [soulOutFilterArray addObject:effectLoadTime];
                    }else{
                        [soulOutFilterArray addObjectsFromArray:effectLoadTime];
                    }
                }
            }
                break;
            case 3:{
                if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
                    id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
                    if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
                        [vertigoFilterArray addObject:effectLoadTime];
                    }else{
                        [vertigoFilterArray addObjectsFromArray:effectLoadTime];
                    }
                }
            }
                break;
            case 4:{
                if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
                    id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
                    if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
                        [scaleFilterArray addObject:effectLoadTime];
                    }else{
                        [scaleFilterArray addObjectsFromArray:effectLoadTime];
                    }
                }
            }
                break;
            default:
                break;
        }
    }];
        
    DWGPUImageShakeFilter * shakeFilter = [[DWGPUImageShakeFilter alloc]init];
    [shakeFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
        DWGPUImageShakeFilter * filter = (DWGPUImageShakeFilter *)output;
        if (shakeFilterArray.count == 0) {
            return;
        }
        //判断是否添加滤镜效果
        CGFloat currentTime = CMTimeGetSeconds(time);
        BOOL isShow = NO;
        for (NSDictionary * effectLoadTimeDict in shakeFilterArray) {
            CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
            CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
            if (currentTime >= beginTime && currentTime <= endTime) {
                isShow = YES;
                break;
            }
        }
        if (isShow) {
            filter.time = currentTime;
        }else{
            filter.time = 0.0;
        }
        
    }];
    
    DWGPUImageFlashFilter * flashFilter = [[DWGPUImageFlashFilter alloc]init];
    [flashFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
        DWGPUImageFlashFilter * filter = (DWGPUImageFlashFilter *)output;
        if (flashFilterArray.count == 0) {
            return;
        }
        //判断是否添加滤镜效果
        CGFloat currentTime = CMTimeGetSeconds(time);
        BOOL isShow = NO;
        for (NSDictionary * effectLoadTimeDict in flashFilterArray) {
            CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
            CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
            if (currentTime >= beginTime && currentTime <= endTime) {
                isShow = YES;
                break;
            }
        }
        if (isShow) {
            filter.time = currentTime;
        }else{
            filter.time = 0.0;
        }
    }];
    
    DWGPUImageSoulOutFilter * soulOutFilter = [[DWGPUImageSoulOutFilter alloc]init];
    [soulOutFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
        DWGPUImageSoulOutFilter * filter = (DWGPUImageSoulOutFilter *)output;
        if (soulOutFilterArray.count == 0) {
            return;
        }
        //判断是否添加滤镜效果
        CGFloat currentTime = CMTimeGetSeconds(time);
        BOOL isShow = NO;
        for (NSDictionary * effectLoadTimeDict in soulOutFilterArray) {
            CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
            CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
            if (currentTime >= beginTime && currentTime <= endTime) {
                isShow = YES;
                break;
            }
        }
        if (isShow) {
            filter.time = currentTime;
        }else{
            filter.time = 0.0;
        }
    }];
    
    DWGPUImageVertigoFilter * vertigoFilter = [[DWGPUImageVertigoFilter alloc]init];
    [vertigoFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
        DWGPUImageVertigoFilter * filter = (DWGPUImageVertigoFilter *)output;
        if (vertigoFilterArray.count == 0) {
            return;
        }
        //判断是否添加滤镜效果
        CGFloat currentTime = CMTimeGetSeconds(time);
        BOOL isShow = NO;
        for (NSDictionary * effectLoadTimeDict in vertigoFilterArray) {
            CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
            CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
            if (currentTime >= beginTime && currentTime <= endTime) {
                isShow = YES;
                break;
            }
        }
        if (isShow) {
            filter.time = currentTime;
        }else{
            filter.time = 0.0;
        }
    }];
    
    DWGPUImageScaleFilter * scaleFilter = [[DWGPUImageScaleFilter alloc]init];
    [scaleFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
        DWGPUImageScaleFilter * filter = (DWGPUImageScaleFilter *)output;
        if (scaleFilterArray.count == 0) {
            return;
        }
        //判断是否添加滤镜效果
        CGFloat currentTime = CMTimeGetSeconds(time);
        BOOL isShow = NO;
        for (NSDictionary * effectLoadTimeDict in scaleFilterArray) {
            CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
            CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
            if (currentTime >= beginTime && currentTime <= endTime) {
                isShow = YES;
                break;
            }
        }
        if (isShow) {
            filter.time = currentTime;
        }else{
            filter.time = 0.0;
        }
    }];
    
    [output addTarget:shakeFilter];
    [shakeFilter addTarget:flashFilter];
    [flashFilter addTarget:soulOutFilter];
    [soulOutFilter addTarget:vertigoFilter];
    [vertigoFilter addTarget:scaleFilter];
    return scaleFilter;
}

-(void)dealEffectsData
{
//    CGFloat duration = CMTimeGetSeconds(self.videoDuration);
    
    for (int i = 0; i < self.effectsArray.count; i++) {
        NSMutableDictionary * effectParams = [self.effectsArray objectAtIndex:i];
        //选中特效时间段
        CGFloat undoBeginTime = [[effectParams objectForKey:@"undoBeginTime"] floatValue];
        CGFloat undoEndTime = [[effectParams objectForKey:@"undoEndTime"] floatValue];
        
        NSMutableArray * effectLoadTimeArray = [NSMutableArray array];
        //添加自身位置
        NSInteger selfLoc = undoBeginTime * 1000000;
        NSInteger selfLen = (undoEndTime - undoBeginTime) * 1000000;
        [effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(selfLoc, selfLen)]];
        
        for (int j = i + 1; j < self.effectsArray.count; j++) {
            NSDictionary * nextEffectParams = [self.effectsArray objectAtIndex:j];
            if ([[nextEffectParams objectForKey:@"alreadyUndo"] boolValue]) {
                //已撤销操作
                continue;
            }
            
            CGFloat nextUndoBeginTime = [[nextEffectParams objectForKey:@"undoBeginTime"] floatValue];
            CGFloat nextUndoEndTime = [[nextEffectParams objectForKey:@"undoEndTime"] floatValue];
            if (undoBeginTime >= nextUndoBeginTime && undoBeginTime <= nextUndoEndTime) {
                if (undoEndTime >= nextUndoEndTime) {
                    //覆盖部分
                    NSInteger loc = nextUndoEndTime * 1000000;
                    NSInteger len = (undoEndTime - nextUndoEndTime) * 1000000;
                    
                    [effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(loc, len)]];
                }else{
                    //全部覆盖
                }
           }else if (nextUndoBeginTime > undoBeginTime && nextUndoBeginTime < undoEndTime) {

                if (undoEndTime < nextUndoEndTime) {
                    //部分覆盖
                    NSInteger loc = undoBeginTime * 1000000;
                    NSInteger len = (nextUndoBeginTime - undoBeginTime) * 1000000;
                    
                    [effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(loc, len)]];

                }else {
                    //截断的情况
                    NSInteger fLoc = undoBeginTime * 1000000;
                    NSInteger fLen = (nextUndoBeginTime - undoBeginTime) * 1000000;
                    NSRange fRange = NSMakeRange(fLoc, fLen);
                    
                    NSInteger sLoc = nextUndoEndTime * 1000000;
                    NSInteger sLen = (undoEndTime - nextUndoEndTime) * 1000000;
                    NSRange sRange = NSMakeRange(sLoc, sLen);
                    
                    [effectLoadTimeArray addObject:@[[NSValue valueWithRange:fRange],[NSValue valueWithRange:sRange]]];
                    
                }
            }else{

                
            }
            
        }
        
//        NSLog(@"effectLoadTimeArray:%@",effectLoadTimeArray);
        
        //处理effectLoadTimeArray 计算交集时间
        id effectLoadTime = [self getEffectTimeIntersectionWithArray:effectLoadTimeArray];
        
        [effectParams setValue:effectLoadTime forKey:@"effectLoadTime"];

    }
}

4.13 Demo具体使用

Demo是示例源码,可直接用Xcode运行。Demo的设计旨在展示SDK各项功能的使用方法,如果希望应用获得更好的使用体验,要根据需求自行更改。 如果在使用SDK过程中遇到其他问题请联系CC客服进行反馈。

5.API 查询

https://hdgit.bokecc.com/ccvideo/VOD_iOS_ShortVideo_SDK/tree/master/doc/api

6.Q&A

Clone repository
  • Home