在游戏中,有时候需要将一些简单的图片组合成视频,然后让用户分享出去,当然仅仅是那种很简单的图片组合视频(这是我自己项目的需求,不需要录制UI等其他东西)。本来想找插件的,后面看到大部分插件都是通过摄像机去录屏,但是我们项目本来就一个对图片操作的游戏,因此没法用插件。
寻思着自己写一个,还好iOS方面的东西还是很齐全的,在查阅了很多资料和博客之后,终于被我弄出来的。
原理也简单,就是通过写iOS原生的视频生成代码,然后通过Unity去调用的方式去做的,Unity本身并不能生成视频!
这里说明下,我写的代码都是在我原来博客写的一个类里面添加的,可以先看看我之前的类:原生分享
添加一个将图片合成视频的方法(这个方法是我在网上找的,自己并不会oc - -!)
- (void) combineImageToVideo2:(NSString*)mainPath byName:(NSString*)videoName imageCount:(int)count
{
NSLog(@"path = %@, video name = %@", mainPath, videoName);
imageArray = [[NSMutableArray alloc]init];
for(int i=0;i<count;i++){
NSString* _pathImage = [mainPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%dtv.png", i]];
NSData* data = [NSData dataWithContentsOfFile:_pathImage];
UIImage* image = [UIImage imageWithData:data];
[imageArray addObject:image];
}
NSString* moviePath = [mainPath stringByAppendingPathComponent:videoName];//@"outputVideo.mov"
self.theMainPath = moviePath;
self.theVideoName = videoName;
//定义视频的大小 256 256 倍数
CGSize size = CGSizeMake(256, 256);
NSError *error = nil;
// 转成UTF-8编码
unlink([moviePath UTF8String]);
// iphone提供了AVFoundation库来方便的操作多媒体设备,AVAssetWriter这个类可以方便的将图像和音频写成一个完整的视频文件
AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:moviePath] fileType:AVFileTypeQuickTimeMovie error:&error];
NSParameterAssert(videoWriter);
if (error){
NSLog(@"error = %@", [error localizedDescription]);
UnitySendMessage("GJCNativeShare", "OnCreateVideoFailed", [GJC_DataConvertor NSStringToChar:@"error: videoWriter error"]);
}
//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(@"can add input");
// UnitySendMessage("GJCNativeShare", "OnCreateVideoWork", [GJC_DataConvertor NSStringToChar:@"write: videoWriter can add input"]);
}
else
{
NSLog(@"can not add input");
UnitySendMessage("GJCNativeShare", "OnCreateVideoFailed", [GJC_DataConvertor NSStringToChar:@"error: videoWriter can not add input"]);
}
[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:^{
BOOL blnError = NO;
while([writerInput isReadyForMoreMediaData])
{
if(++frame >= [imageArray count])
{
[writerInput markAsFinished];
// [videoWriter finishWriting];
// [videoWriter finishWritingWithCompletionHandler:nil];
[videoWriter finishWritingWithCompletionHandler:^{
NSLog(@"creat video finished!");
}];
if (blnError){
UnitySendMessage("GJCNativeShare", "OnCreateVideoFailed", [GJC_DataConvertor NSStringToChar:@"Failed"]);
}else{
UnitySendMessage("GJCNativeShare", "OnCreateVideoSuccess", [GJC_DataConvertor NSStringToChar:@"Success"]);
}
break;
}
CVPixelBufferRef buffer = NULL;
int idx = frame;
NSLog(@"idx==%d",idx);
buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:[[imageArray objectAtIndex:idx] CGImage] size:size];
if (buffer)
{
//设置每秒钟播放图片的个数
if(![adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(frame, 12)])
{
NSLog(@"FAIL");
// UnitySendMessage("GJCNativeShare", "OnCreateVideoWork", [GJC_DataConvertor NSStringToChar:@"error: adaptor add error"]);
blnError = YES;
}
else
{
// NSLog(@"OK");
// UnitySendMessage("GJCNativeShare", "OnCreateVideoWork", [GJC_DataConvertor NSStringToChar:@"write: adaptor add 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;
}
再添加个C方法:
extern "C" {
void _GJC_CombineImageToVideo(char* mainPath, char* videoName, int count) {
NSString *savePath = [GJC_DataConvertor charToNSString:mainPath];
NSString *saveName = [GJC_DataConvertor charToNSString:videoName];
[[GJCSocialShare sharedInstance] combineImageToVideo2:savePath byName:saveName imageCount:count];
}
}
Unity添加方法:
[DllImport ("__Internal")]
private static extern void _GJC_CombineImageToVideo(string savePath, string videoName, int count);
封装的方法:
//视频图片临时存放位置
public static string filePath{
get{
return Application.persistentDataPath + "/TempVideo";
}
}
public static string videoPath{
get{
return GJCNativeShare.filePath + "/" + videoName;
}
}
public static string videoName = "outputVideo.mov";
/// <summary>
/// 需要将图片保存在filePath文件夹下,然后命名为 i + tv.png,从序号0开始
/// 图片的尺寸为 256x256
/// 最终在文件目录下得到一个 outputVideo.mov 的文件,这个就是我们需要的视频文件了,可以将这个文件的地址分享出去
/// 或者保存到相册中去
/// </summary>
/// <param name="count"></param>
public void CombineImageToVideo(string videoname, int count){
// Debug.Log("Native Combine Image to video");
#if UNITY_IPHONE && !UNITY_EDITOR
_GJC_CombineImageToVideo(filePath + "/", videoname, count);
#endif
}
添加回调:
private void OnCreateVideoFailed(string result){
// Debug.Log("OnCreateVideoFailed: " + result);
if (onCreateVideo != null){
onCreateVideo("Failed");
}
}
private void OnCreateVideoWork(string result){
// Debug.Log("OnCreateVideoWork: " + result);
if (onCreateVideo != null){
onCreateVideo("Work");
}
}
private void OnCreateVideoSuccess(string result){
// Debug.Log("OnCreateVideoSuccess: " + result);
if (onCreateVideo != null){
onCreateVideo("Success");
}
}
外部调用:
/// <summary>
/// 将固定路径下的图片,合成视频
/// </summary>
/// <param name="count">传递图片数量</param>
/// <param name="cb">回调函数</param>
public void CreateVideo(string videoName, int count, OnNativeCallback cb){
createVideoCB = cb;
if (Application.platform == RuntimePlatform.IPhonePlayer){
GJCNativeShare.Instance.CombineImageToVideo(videoName, count);
}else{
OnCreateVideo("Success");
}
}
上面GJCNativeShare是我写的接入类,可以参考上面的链接。
我这个是设置的1秒12帧,具体需要多少,可以在代码里面找到这句话
[adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(frame, 12)]
如果需要在某一帧停留长一些,可以将这个图片重复生成一张,插在序列中,这样就能达到目的。我这里的实现方式,是在Unity端先把图片生成好,保存在一个路径下面,我是保存在 Application.persistentDataPath 的一个临时文件夹里面,图片的名字按照规定好的形式命名,然后将路径和图片的数量传递到iOS端,这边将图片文件的读取并且经过合成之后,返回结果(两边保存的视频的名字我这里也是写死的“outputVideo.mov”,可以根据需要,在传递参数到iOS的时候可以增加一个,我是觉得临时弄的,就没加了,看个人需求了)
之后在Unity这边就能拿到这个视频了,可以通过video play加载展现出来,也可以将这个本地视频分享出去。至于分享,如果是利用的原生弹框,只需要将这个视频的本地路径传递进去就好了。如果想分享到Instagram上,可以看我的另一个文章,可以直接分享到Instagram的编辑状态,很好用,传送:分享到Instagram
--------------底线-------------