Unity3D-iOS创建本地视频

在游戏中,有时候需要将一些简单的图片组合成视频,然后让用户分享出去,当然仅仅是那种很简单的图片组合视频(这是我自己项目的需求,不需要录制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

--------------底线-------------

猜你喜欢

转载自blog.csdn.net/pz789as/article/details/81326242