随着市场直播的日益发展,现在市场上媒体流的重要性被越来越多的开发者重视。本菜鸟在上个项目的开发中,很是尴尬的也遇到的视频流以及音频流的处理、上传。当中也遇到的不少坑,因为当初的项目是社交类的服务项目,需要用户能够上传视频、语音等,所以涉及到音视频的处理。
时间过得很快,2016年马上过去,最近又比较闲。所有用有限的时间总结一下这一年多来遇到的一些比较有意思的知识点与大家共勉,希望各路大神多多指教,不喜勿喷。谢谢。本菜鸟必将再去奋斗。
这边文章分享一些本菜鸟的对视频的一些处理,音频后面有时间在上来分享共勉。
视频获取上传方式
1:直接摄像头拍摄,压缩上传给服务端
2:从相册中选取已有视频,压缩上传给服务端。
第一种方法好处理,拍摄完直接拿到视频流和路径了,这个应该不难。
第二种方法,我在做项目的时候遇到不少坑(项目兼容7.0以上),所有在获取视频流的时候,8.0以上、8.0以下的方法不一样,被坑了。
下面直接贴代码并解释,但愿有遇到的朋友可以互勉,本菜鸟也是菜鸟一个,做的也不是很好。
-------------------1:直接拍摄视频,上传服务端--------------------
#pragma mark 用户拍摄视频-(void)shotVideoEvent{ if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { UIImagePickerController *videoPicker = [[UIImagePickerController alloc] init]; videoPicker.delegate = self; videoPicker.allowsEditing = YES; videoPicker.sourceType = UIImagePickerControllerSourceTypeCamera; NSArray *mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; videoPicker.videoQuality = UIImagePickerControllerQualityTypeMedium; videoPicker.videoMaximumDuration = 1200.0f;//1200秒 20分钟 本项目只允许最长时间 //videoPicker.mediaTypes = mediaTypes; videoPicker.mediaTypes = [NSArray arrayWithObject:@"public.movie"]; [self presentViewController:videoPicker animated:YES completion:nil]; }else{ UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"设备不支持拍照" message:nil delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil]; [alertView show]; }}#pragma mark 拍摄视频完的方法- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];//视频路径 NSData *beforeVideoData = [NSData dataWithContentsOfURL:videoURL];//未压缩的视频流 //这里是视频流处理的方法 下面会解释 VideoServer *videoServer = [[VideoServer alloc] init]; NSString *randomName = @"随便给视频取个名字吧";//当然自己的项目不是这样写的,这里为了解释 [videoServer compressVideo:videoURL andVideoName:randomName andSave:YES successCompress:^(NSData *resultData) { //这里就是视频流压缩后回调的方法 if (resultData == nil || resultData.length == 0 ) { //如果压缩失败,我就把未压缩的视频流直接上传给服务端咯 [self postFormData:beforeVideoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; }else{ //压缩成功的视频流 上传给服务端 [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; }//这里我发现拍摄的视频,压缩前压缩后大小差不多,会不会是拍摄完苹果已经做处理? }]; [picker dismissViewControllerAnimated:YES completion:nil];}
-------------------2:从相册选取视频,上传服务端--------------------
#pragma mark 用户选择好了视频 这里项目需求用到了一个第三方的选取照片和视频《TZImagePickerController》,感谢作者,但是在使用的时候也遇到有bug,不能满足项目需求,自己做了部分修改,谢谢作者分享这个选取照片视频的- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(id)asset{ NSString *randomName = @"随便给视频取个名字吧";//当然自己的项目不是这样写的,这里为了解释 //下面本菜鸟遇到的坑-->视频流路径竟然不一样,分iOS 8 以上、以下视频流的获取处理 if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0f) { //如果是iOS 8以上 PHAsset *myAsset = asset; PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; [[PHImageManager defaultManager] requestAVAssetForVideo:myAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) { NSURL *fileRUL = [asset valueForKey:@"URL"]; NSData *beforeVideoData = [NSData dataWithContentsOfURL:fileRUL];//未压缩的视频流 VideoServer *videoServer = [[VideoServer alloc] init]; [videoServer compressVideo:fileRUL andVideoName:randomName andSave:NO successCompress:^(NSData *resultData) { if (resultData == nil || resultData.length == 0 ) { [self postFormData:beforeVideoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; }else{ [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; } }]; }]; }else{//如果是iOS 8以下获取视频流 ALAssetRepresentation *rep = [asset defaultRepresentation];//视频流路径 VideoServer *videoServer = [[VideoServer alloc] init]; [videoServer compressVideo:rep.url andVideoName:randomName andSave:NO successCompress:^(NSData *resultData) { if (resultData == nil || resultData.length == 0 ) { //iOS 8 以下 压缩失败 直接获取未压缩的视频流 ALAssetRepresentation *rep = [asset defaultRepresentation]; Byte *buffer = (Byte *)malloc(rep.size); NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil]; NSData *videoData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES]; [self postFormData:videoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; }else{ //如果压缩成功 就用压缩的上传服务端,但是注意了 这里如果不用异步的,上传的时候程序挂了 //ios 8 以下一定必须加入这个再去请求,否则会挂 dispatch_async(dispatch_get_main_queue(),^{ [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]]; }); } }]; }}
----------------下面是视频压缩的方法以及获取视频首帧缩略图的类-----------------
#import <Foundation/Foundation.h>@interface VideoServer : NSObject///视频名字@property (nonatomic,strong) NSString *videoName;/// 压缩视频-(void)compressVideo:(NSURL *)path andVideoName:(NSString *)name andSave:(BOOL)saveState successCompress:(void(^)(NSData *))successCompress;/// 获取视频的首帧缩略图- (UIImage *)imageWithVideoURL:(NSURL *)url;@end
#import "VideoServer.h"#import <Photos/Photos.h>#import <AssetsLibrary/AssetsLibrary.h>@implementation VideoServer//压缩视频-(void)compressVideo:(NSURL *)path andVideoName:(NSString *)name andSave:(BOOL)saveState successCompress:(void(^)(NSData *))successCompress //saveState 是否保存视频到相册{ self.videoName = name; AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:path options:nil]; NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) { AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480]; exportSession.outputURL = [self compressedURL];//设置压缩后视频流导出的路径 exportSession.shouldOptimizeForNetworkUse = true; //转换后的格式 exportSession.outputFileType = AVFileTypeMPEG4; //异步导出 [exportSession exportAsynchronouslyWithCompletionHandler:^{ // 如果导出的状态为完成 if ([exportSession status] == AVAssetExportSessionStatusCompleted) { //NSLog(@"视频压缩成功,压缩后大小 %f MB",[self fileSize:[self compressedURL]]); if (saveState) { [self saveVideo:[self compressedURL]];//保存视频到相册 } //压缩成功视频流回调回去 successCompress([NSData dataWithContentsOfURL:[self compressedURL]].length > 0?[NSData dataWithContentsOfURL:[self compressedURL]]:nil); }else{ //压缩失败的回调 successCompress(nil); } }]; }}#pragma mark 保存压缩- (NSURL *)compressedURL{ return [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",self.videoName]]];}#pragma mark 计算视频大小- (CGFloat)fileSize:(NSURL *)path{ return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00;}#pragma mark 保存视频到相册- (void)saveVideo:(NSURL *)outputFileURL{ ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) { if (error) { //(@"保存视频失败:%@",error); } else { //NSLog(@"保存视频到相册成功"); } }];}/** * 通过视频的URL,获得视频缩略图 * @param url 视频URL * @return首帧缩略图 */#pragma mark 获取视频的首帧缩略图- (UIImage *)imageWithVideoURL:(NSURL *)url{ NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:opts]; // 根据asset构造一张图 AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; // 设定缩略图的方向 // 如果不设定,可能会在视频旋转90/180/270°时,获取到的缩略图是被旋转过的,而不是正向的(自己的理解) generator.appliesPreferredTrackTransform = YES; // 设置图片的最大size(分辨率) generator.maximumSize = CGSizeMake(600, 450); NSError *error = nil; // 根据时间,获得第N帧的图片 // CMTimeMake(a, b)可以理解为获得第a/b秒的frame CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(0, 10000) actualTime:NULL error:&error]; UIImage *image = [UIImage imageWithCGImage: img]; return image;}@end
---------------------------------总结一下-------------------------------
本菜鸟的一些总结,当然分享出来的跟自己项目的做了适当的修改,目的就是分享共勉,希望大神多多指教,不喜勿喷,如果可以对您有好处,那本菜鸟已心满意足。
获取视频的首帧缩略图,是因为本菜鸟的项目需要,服务端需要客户端上传一张图片,用于后面页面显示视频的,所有...