... | ... | @@ -14,8 +14,9 @@ iOS短视频SDK是适用于iOS平台的短视频SDK。使用此SDK可以实现 |
|
|
| 视频水印 | 支持根据时段,位置添加视频水印 |
|
|
|
| 气泡文字 | 支持根据时段,位置添加气泡文字 |
|
|
|
| 音频插入 | 支持对视频插入背景音乐 |
|
|
|
| 视频大小修改 | 支持修改视频分辨率 |
|
|
|
| 视频大小修改 | 支持修改视频分辨率 |
|
|
|
| 图片合成视频 | 支持多图片合成视频,添加动效 |
|
|
|
| 视频特效 | 支持视频特效添加 |
|
|
|
|
|
|
## 1.2 阅读对象
|
|
|
本文档为技术文档,需要阅读者:
|
... | ... | @@ -980,8 +981,331 @@ instrument.complete = ^(NSError *error, NSURL *completeFileURL) { |
|
|
|
|
|
```
|
|
|
|
|
|
## 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.12 Demo具体使用
|
|
|
## 4.13 Demo具体使用
|
|
|
Demo是示例源码,可直接用Xcode运行。Demo的设计旨在展示SDK各项功能的使用方法,如果希望应用获得更好的使用体验,要根据需求自行更改。
|
|
|
如果在使用SDK过程中遇到其他问题请联系CC客服进行反馈。
|
|
|
|
... | ... | |