iOS開發(fā)-美顏相機(jī)、短視頻(GPUImage的使用)

修改了GPUImage導(dǎo)入的方式燃观,也解決了視頻編輯后出現(xiàn)90旋轉(zhuǎn)的問(wèn)題褒脯。
網(wǎng)上流行的美顏濾鏡有很多,我的demo里有兩款美顏濾鏡(GPUImageBeautifyFilter缆毁、FSKGPUImageBeautyFilter)番川。
我本意是準(zhǔn)備寫一個(gè)相冊(cè)管理的,最后由于項(xiàng)目需求脊框,寫成了短視頻處理颁督,所以項(xiàng)目名字沒(méi)改,demo下面會(huì)給出缚陷,只是給大家一個(gè)相關(guān)思路适篙,不惜勿噴往核。下面直接上我demo里比較重要的幾個(gè)地方:
短視頻功能使用的是GPUImageVideoCamera箫爷,由于項(xiàng)目里需要使用1:1,所以使用了濾鏡GPUImageCropFilter聂儒,大家如果有需要虎锚,可以更高這個(gè)濾鏡的設(shè)置,下面給出相機(jī)的設(shè)置代碼:

//相機(jī)設(shè)置
- (void)customSystemSession {
    WS(weakSelf)
    self.imgView = [UIImageView new];
    self.imgView.clipsToBounds = YES;
    [self.view addSubview:self.imgView];
    [self.imgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(weakSelf.topView.mas_bottom).offset(0);
        make.left.equalTo(weakSelf.view.mas_left).offset(0);
        make.right.equalTo(weakSelf.view.mas_right).offset(0);
        make.height.mas_equalTo(SCREEN_WIDTH);
    }];
    
    //美顏相機(jī)
    self.kj_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionBack];
    self.kj_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
    self.kj_videoCamera.horizontallyMirrorFrontFacingCamera = YES;
    self.kj_filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_WIDTH)];
    self.kj_filterView.fillMode = kGPUImageFillModePreserveAspectRatioAndFill;
    //    self.filterView.center = self.view.center;
    [self.imgView addSubview:self.kj_filterView];
    
    
//    //剪裁濾鏡(1:1)如果有需要全屏衩婚,這里需要做出更改或者直接去掉這個(gè)濾鏡
    self.kj_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0, 44/SCREEN_HEIGHT, 1, SCREEN_WIDTH/SCREEN_HEIGHT)];
    //美顏(默認(rèn)開啟美顏)
    self.kj_beautifyFilter = [[FSKGPUImageBeautyFilter alloc] init];
    self.kj_beautifyFilter.beautyLevel = 0.9f;//美顏程度
    self.kj_beautifyFilter.brightLevel = 0.7f;//美白程度
    self.kj_beautifyFilter.toneLevel = 0.9f;//色調(diào)強(qiáng)度
    
//    self.kj_filter = [[GPUImageSaturationFilter alloc] init];
//    //濾鏡組
//    self.kj_filterGroup = [[GPUImageFilterGroup alloc] init];
//    [self.kj_filterGroup addFilter:self.kj_cropFilter];
//    [self.kj_filterGroup addFilter:self.kj_beautifyFilter];
    
//    [self openBeautify];

    [self.kj_videoCamera addAudioInputsAndOutputs];
    [self.kj_videoCamera addTarget:self.kj_cropFilter];
    [self.kj_cropFilter addTarget:self.kj_beautifyFilter];
    [self.kj_beautifyFilter addTarget:self.kj_filterView];
    
    [self.kj_videoCamera startCameraCapture];
}

對(duì)于相機(jī)的前后鏡頭切換窜护、閃光燈等等這里就不做說(shuō)明了,對(duì)于短視頻我的處理方案是多段視頻合并非春,用GPUImageVideoCamera來(lái)錄制多段視頻柱徙,帶美顏濾鏡缓屠,其實(shí)這里是可以直接使用實(shí)時(shí)濾鏡的,但是由于公司只需要美顏护侮,所以這里沒(méi)做其它濾鏡的處理(實(shí)際上和美顏濾鏡的切換時(shí)一樣的道理敌完,所以實(shí)時(shí)濾鏡實(shí)際上是在每段視頻拍攝前選好濾鏡后addTarget就好了,當(dāng)然啦羊初,如果是混合使用后滨溉,要先刪除(removeTarget)后添加),但是在后續(xù)我單獨(dú)寫了一個(gè)視頻的簡(jiǎn)單編輯功能长赞,那里有對(duì)于本地視頻晦攒、圖片增加濾鏡的處理,后續(xù)會(huì)講到得哆,接下來(lái)放上短視頻中最重要的部分脯颜,視頻合并:

//確定(完成)
- (void)onCompleteAction:(UIButton *)sender {
    if (self.kj_videoArray.count > 0) {
        if (self.kj_videoArray.count > 1) {
            //需要合并多段視頻
            if (!self.kj_outPath) {
                self.kj_outPath = [self getVideoOutPath];//合成后的輸出路徑
            }
            //判斷本地是否已有合成后的視頻文件
            if ([[NSFileManager defaultManager] fileExistsAtPath:self.kj_outPath]) {
                //如果存在就刪除,重新合成
                [[NSFileManager defaultManager] removeItemAtPath:self.kj_outPath error:nil];
            }
            //音視頻合成工具
            AVMutableComposition *kj_composition = [AVMutableComposition composition];
            //音頻
            AVMutableCompositionTrack *kj_audioTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
            //視頻
            AVMutableCompositionTrack *kj_videoTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
            //開始合成
            [KJUtility showProgressDialogText:@"視頻處理中..."];
            CMTime kj_totalDuration = kCMTimeZero;
            for (int i = 0; i < self.kj_videoArray.count; i ++) {
                NSDictionary *localDict = self.kj_videoArray[i];
                NSDictionary* options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
                
                AVAsset *kj_asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:localDict[@"path"]] options:options];
                //獲取kj_asset中的音頻
                NSArray *audioArray = [kj_asset tracksWithMediaType:AVMediaTypeAudio];
                AVAssetTrack *kj_assetAudio = audioArray.firstObject;
                //向kj_audioTrack中加入音頻
                NSError *kj_audioError = nil;
                BOOL isComplete_audio = [kj_audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, kj_asset.duration)
                                                               ofTrack:kj_assetAudio
                                                                atTime:kj_totalDuration
                                                                 error:&kj_audioError];
                NSLog(@"加入音頻%d  isComplete_audio:%d error:%@", i, isComplete_audio, kj_audioError);
                
                //獲取kj_asset中的視頻
                NSArray *videoArray = [kj_asset tracksWithMediaType:AVMediaTypeVideo];
                AVAssetTrack *kj_assetVideo = videoArray.firstObject;
                //向kj_videoTrack中加入視頻
                NSError *kj_videoError = nil;
                BOOL isComplete_video = [kj_videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, kj_asset.duration)
                                                               ofTrack:kj_assetVideo
                                                                atTime:kj_totalDuration
                                                                 error:&kj_videoError];
                NSLog(@"加入視頻%d  isComplete_video:%d error:%@", i, isComplete_video, kj_videoError);
                
                kj_totalDuration = CMTimeAdd(kj_totalDuration, kj_asset.duration);
            }
            //這里可以加水印的柳恐,但在這里不做水印處理
            
            //視頻導(dǎo)出處理
            AVAssetExportSession *kj_export = [AVAssetExportSession exportSessionWithAsset:kj_composition
                                                                                presetName:AVAssetExportPreset1280x720];
            kj_export.outputURL = [NSURL fileURLWithPath:self.kj_outPath];
            kj_export.outputFileType = AVFileTypeMPEG4;
            kj_export.shouldOptimizeForNetworkUse = YES;
            WS(weakSelf)
            [kj_export exportAsynchronouslyWithCompletionHandler:^{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [KJUtility hideProgressDialog];
                    if (weakSelf.kjFileDelegate && [weakSelf.kjFileDelegate respondsToSelector:@selector(kj_videoFileCompleteLocalPath:)]) {
                        //合成視頻成功后伐脖,刪除小段視頻
                        [weakSelf clearAllVideo];
                        NSLog(@"%@",weakSelf.kj_outPath);
                        [weakSelf.kjFileDelegate kj_videoFileCompleteLocalPath:weakSelf.kj_outPath];
                    } else {
                        [weakSelf saveVideoToLibrary];
                    }
                });
            }];
        } else {
            //只有一段視頻
            [KJUtility hideProgressDialog];
            NSDictionary *dict = self.kj_videoArray.firstObject;
            self.kj_outPath = dict[@"path"];
            if (self.kjFileDelegate && [self.kjFileDelegate respondsToSelector:@selector(kj_videoFileCompleteLocalPath:)]) {
                [self.kjFileDelegate kj_videoFileCompleteLocalPath:self.kj_outPath];
            } else {
                [self saveVideoToLibrary];
            }
        }
    }
}

注釋都說(shuō)的很清楚了,就不對(duì)代碼做相關(guān)解釋了乐设,最主要的還是AVMutableComposition讼庇、AVMutableCompositionTrackAVMutableCompositionTrack的簡(jiǎn)單實(shí)用近尚,百度一下就能看到相關(guān)屬性的說(shuō)明蠕啄,其它的就和普通相機(jī)的處理沒(méi)什么不同的。
另一個(gè)方面戈锻,就是視頻的剪輯了歼跟,大家對(duì)于上傳服務(wù)器的視頻,由于時(shí)間太長(zhǎng)或者是視頻太大格遭,這就需要對(duì)視頻進(jìn)行壓縮和裁剪處理哈街,這里對(duì)于時(shí)間的選取UI層上不做說(shuō)明了,可以看的demo里的UI處理拒迅,下面給出視頻剪裁的代碼:

- (void)onCompleteButtonAction:(UIButton *)sender {
    
    //開始剪裁
    [self.kj_player pause];
    //開始時(shí)間
    CMTime startTime = CMTimeMakeWithSeconds((self.collectionView.contentOffset.x+self.btnStart.frame.origin.x)*self.pixel_time, self.kj_player.currentItem.duration.timescale);
    //長(zhǎng)度
    CGFloat length = (self.btnEnd.frame.origin.x+self.btnEnd.frame.size.width-self.btnStart.frame.origin.x)*self.pixel_time;
    CMTime time_total = [self.kj_player.currentItem duration];
    if (length == 1.0*time_total.value/time_total.timescale) {
        if (self.kj_videoCapturedelegate && [self.kj_videoCapturedelegate respondsToSelector:@selector(kj_didCaptureCompleteForPath:)]) {
            AVURLAsset *urlAsset = (AVURLAsset *)self.kj_player.currentItem.asset;
            [self.kj_videoCapturedelegate kj_didCaptureCompleteForPath:urlAsset.URL.path];
        } else {
            [self onCancelButtonAction:nil];
        }
        return;
    }
    
    [KJUtility showProgressDialogText:@"開始處理"];
    if (length > self.kj_maxTime) {
        length = self.kj_maxTime;
    }
    CMTime videoLenth = CMTimeMakeWithSeconds(length, self.kj_player.currentItem.duration.timescale);
    
    CMTimeRange videoTimeRange = CMTimeRangeMake(startTime, videoLenth);
    AVAssetExportSession * exportSession = [[AVAssetExportSession alloc] initWithAsset:self.kj_player.currentItem.asset presetName:AVAssetExportPresetMediumQuality];
    exportSession.timeRange = videoTimeRange;
    NSString *path = [KJUtility kj_getKJAlbumFilePath];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyyMMddHHmmss"];
    NSString *fileName = [NSString stringWithFormat:@"%@-%@",[formatter stringFromDate:[NSDate date]], @"kj_video.mp4"];
    path = [path stringByAppendingPathComponent:fileName];
    exportSession.outputURL = [NSURL fileURLWithPath:path];
    exportSession.outputFileType = AVFileTypeMPEG4;
    __block BOOL completeOK = NO;
    WS(weakSelf)
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        switch (exportSession.status) {
            case AVAssetExportSessionStatusUnknown:
                break;
            case AVAssetExportSessionStatusWaiting:
                break;
            case AVAssetExportSessionStatusExporting:
                break;
            case AVAssetExportSessionStatusCompleted:
                completeOK = YES;
                break;
            case AVAssetExportSessionStatusFailed:
                break;
            case AVAssetExportSessionStatusCancelled:
                break;
        };
        
        if (completeOK) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [KJUtility showAllTextDialog:weakSelf.view Text:@"視頻截取成功"];
                if (weakSelf.kj_videoCapturedelegate && [weakSelf.kj_videoCapturedelegate respondsToSelector:@selector(kj_didCaptureCompleteForPath:)]) {
                    [KJUtility hideProgressDialog];
                    [weakSelf.kj_videoCapturedelegate kj_didCaptureCompleteForPath:path];
                } else {
                    //保存到相冊(cè)
                    [KJUtility kj_saveVideoToLibraryForPath:path completeHandler:^(NSString *localIdentifier, BOOL isSuccess) {
                        if (isSuccess) {
                            NSFileManager *fileManger = [[NSFileManager alloc] init];
                            [fileManger removeItemAtPath:path error:nil];
                        }
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [KJUtility hideProgressDialog];
                            [weakSelf onCancelButtonAction:nil];
                        });
                    }];
                }
            });
        } else {
            [KJUtility showAllTextDialog:weakSelf.view Text:@"視頻截取失敗"];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [KJUtility hideProgressDialog];
        });
    }];
}

接下來(lái)說(shuō)說(shuō)利用GPUImage對(duì)本地視頻加濾鏡骚秦,如果需要選取濾鏡后,能實(shí)時(shí)看到濾鏡效果璧微,需要用到GPUImageMovie來(lái)預(yù)覽作箍,濾鏡合成還需要使用到GPUImageMovieWriter,由于GPUImageMovie預(yù)覽視頻時(shí)是沒(méi)有聲音的前硫,所以還需要一個(gè)播放器來(lái)播放視頻胞得,這個(gè)可以看我的demo處理,下面貼出對(duì)本地視頻合成濾鏡的處理:

//合成濾鏡
- (void)filterCompositionForFilter:(GPUImageOutput<GPUImageInput> *)filter withVideoUrl:(NSURL *)videoUrl {
    if (videoUrl) {
        WS(weakSelf)
        GPUImageOutput<GPUImageInput> *tmpFilter = filter;
        kj_movieComposition = [[GPUImageMovie alloc] initWithURL:videoUrl];
        kj_movieComposition.runBenchmark = YES;
        kj_movieComposition.playAtActualSpeed = NO;

        [kj_movieComposition addTarget:tmpFilter];
        //合成后的視頻路徑
        NSString *newPath = [KJUtility kj_getKJAlbumFilePath];
        newPath = [newPath stringByAppendingPathComponent:[KJUtility kj_getNewFileName]];
        unlink([newPath UTF8String]);
        NSLog(@"%f,%f",self.kj_player.currentItem.presentationSize.height,self.kj_player.currentItem.presentationSize.width);
        CGSize videoSize = self.kj_player.currentItem.presentationSize;
        NSURL *tmpUrl = [NSURL fileURLWithPath:newPath];
        [self.kj_newVideoPathArray addObject:tmpUrl];
        kj_movieWriter  = [[GPUImageMovieWriter alloc] initWithMovieURL:tmpUrl size:videoSize];
        kj_movieWriter.shouldPassthroughAudio = YES;
        //這里原GPUImage始沒(méi)有這個(gè)屬性的屹电,修改后才有
        kj_movieWriter.allowWriteAudio = YES;
        kj_movieComposition.audioEncodingTarget = kj_movieWriter;
        [tmpFilter addTarget:kj_movieWriter];
        [kj_movieComposition enableSynchronizedEncodingUsingMovieWriter:kj_movieWriter];

        [kj_movieWriter startRecording];
        [kj_movieComposition startProcessing];

        __weak GPUImageMovieWriter *weakmovieWriter = kj_movieWriter;
        [kj_movieWriter setCompletionBlock:^{
            NSLog(@"濾鏡添加成功");
            [tmpFilter removeTarget:weakmovieWriter];
            [weakmovieWriter finishRecording];
            dispatch_async(dispatch_get_main_queue(), ^{
                if (weakSelf.kj_selectedMusic) {
                    //合成音樂(lè)
                    [weakSelf musicCompositionForMusicInfo:weakSelf.kj_selectedMusic withVideoPath:weakSelf.kj_newVideoPathArray.lastObject];
                } else {
                    [weakSelf saveVideoToLib];
                }
            });
        }];
        [kj_movieWriter setFailureBlock:^(NSError *error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"濾鏡添加失斀捉!:%@", error);
                if ([[NSFileManager defaultManager] fileExistsAtPath:newPath]) {
                    NSError *delError = nil;
                    [[NSFileManager defaultManager] removeItemAtPath:newPath error:&delError];
                    if (delError) {
                        NSLog(@"刪除沙盒路徑失斣狙病:%@", delError);
                    }
                }
                [weakSelf.kj_newVideoPathArray removeLastObject];
                [KJUtility hideProgressDialog];
            });
        }];
    }
}

下面來(lái)說(shuō)說(shuō)對(duì)于本地視頻添加音樂(lè)的處理,添加音樂(lè)這里我的注釋寫的很清楚牧愁,直接上代碼(還是利用前面提到的是哪個(gè)類來(lái)處理的瓷炮,原理就是把視頻和音頻單獨(dú)提出來(lái),最后放在工具里合并):

//合成音樂(lè)
- (void)musicCompositionForMusicInfo:(NSDictionary *)musicInfo withVideoPath:(NSURL *)videoUrl {
    if (musicInfo && videoUrl) {
        //音樂(lè)
        NSString *audioPath = [[NSBundle mainBundle] pathForResource:musicInfo[@"music"] ofType:@"mp3"];
        NSURL *audioUrl = [NSURL fileURLWithPath:audioPath];
        
        //合成后的視頻輸出路徑
        NSString *newPath = [KJUtility kj_getKJAlbumFilePath];
        newPath = [newPath stringByAppendingPathComponent:[KJUtility kj_getNewFileName]];
        unlink([newPath UTF8String]);
        NSURL *newVideoPath = [NSURL fileURLWithPath:newPath];
        
        //合成工具
        AVMutableComposition *kj_composition = [AVMutableComposition composition];
        //音頻
        AVMutableCompositionTrack *kj_audioTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
        //視頻
        AVMutableCompositionTrack *kj_videoTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
        
        NSDictionary* kj_options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
        //視頻AVAsset
        AVURLAsset *kj_videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:kj_options];
        //視頻時(shí)間范圍(合成的音樂(lè)不能超過(guò)這個(gè)時(shí)間范圍)
        CMTimeRange kj_videoTimeRange = CMTimeRangeMake(kCMTimeZero, kj_videoAsset.duration);
        //采集kj_videoAsset中的視頻
        NSArray *videoArray = [kj_videoAsset tracksWithMediaType:AVMediaTypeVideo];
        AVAssetTrack *kj_assetVideo = videoArray.firstObject;
        //采集的視頻加入到視頻通道kj_videoTrack
        NSError *kj_videoError = nil;
        BOOL isComplete_video = [kj_videoTrack insertTimeRange:kj_videoTimeRange
                                                       ofTrack:kj_assetVideo
                                                        atTime:kCMTimeZero
                                                         error:&kj_videoError];
        NSLog(@"加入視頻isComplete_video:%d error:%@",isComplete_video, kj_videoError);
        //音頻AVAsset
        AVURLAsset *kj_audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:kj_options];
        //采集kj_audioAsset中的音頻
        NSArray *audioArray = [kj_audioAsset tracksWithMediaType:AVMediaTypeAudio];
        AVAssetTrack *kj_assetAudio = audioArray.firstObject;
        //音頻的范圍
        CMTimeRange kj_audioTimeRange = CMTimeRangeMake(kCMTimeZero, kj_audioAsset.duration);
        if (CMTimeCompare(kj_audioAsset.duration, kj_videoAsset.duration)) {//當(dāng)視頻時(shí)間小于音頻時(shí)間
            kj_audioTimeRange = CMTimeRangeMake(kCMTimeZero, kj_videoAsset.duration);
        }
        //采集的音頻加入到音頻通道kj_audioTrack
        NSError *kj_audioError = nil;
        BOOL isComplete_audio = [kj_audioTrack insertTimeRange:kj_audioTimeRange
                                                       ofTrack:kj_assetAudio
                                                        atTime:kCMTimeZero
                                                         error:&kj_audioError];
        NSLog(@"加入音頻isComplete_audio:%d error:%@",isComplete_audio, kj_audioError);
        
        //因?yàn)橐4嫦鄡?cè)递宅,所以設(shè)置高質(zhì)量, 這里可以根據(jù)實(shí)際情況進(jìn)行更改
        WS(weakSelf)
        [KJUtility kj_compressedVideoAsset:kj_composition withPresetName:AVAssetExportPresetHighestQuality withNewSavePath:newVideoPath withCompleteBlock:^(NSError *error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (error) {
                    NSLog(@"轉(zhuǎn)碼失斈锵恪:%@", error);
                    [KJUtility hideProgressDialog];
                } else {
                    [weakSelf.kj_newVideoPathArray addObject:newVideoPath];
                    [weakSelf saveVideoToLib];
                }
            });
        }];
    }
}

視頻壓縮這個(gè)不用多說(shuō)了,網(wǎng)上太多了办龄,基本上是iOS自帶的就有這個(gè)方法烘绽,很簡(jiǎn)單的幾行代碼:

/**
 視頻轉(zhuǎn)碼/壓縮

 @param asset AVAsset
 @param presetName 視頻質(zhì)量(建議壓縮使用AVAssetExportPresetMediumQuality,存相冊(cè)AVAssetExportPreset1920x1080俐填,根據(jù)需求設(shè)置)
 @param savePath 保存的路徑
 @param completeBlock 返回狀態(tài)
 */
+ (void)kj_compressedVideoAsset:(AVAsset *)asset
                 withPresetName:(NSString *)presetName
                withNewSavePath:(NSURL *)savePath
              withCompleteBlock:(void(^)(NSError *error))completeBlock {
    AVAssetExportSession *kj_export = [AVAssetExportSession exportSessionWithAsset:asset
                                                                        presetName:presetName];
    kj_export.outputURL = savePath;
    kj_export.outputFileType = AVFileTypeMPEG4;
    kj_export.shouldOptimizeForNetworkUse = YES;
    [kj_export exportAsynchronouslyWithCompletionHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            if (kj_export.status == AVAssetExportSessionStatusCompleted) {
                if (completeBlock) {
                    completeBlock(nil);
                }
            } else if (kj_export.status == AVAssetExportSessionStatusFailed) {
                if (completeBlock) {
                    completeBlock(kj_export.error);
                }
            } else {
                NSLog(@"當(dāng)前壓縮進(jìn)度:%f",kj_export.progress);
            }
        });
    }];
}

這里說(shuō)的都是短視頻的處理方案安接,事實(shí)上demo里有相冊(cè)的處理,但是沒(méi)做實(shí)時(shí)更新的處理英融,所以有實(shí)時(shí)更新的需要你自己添加代碼監(jiān)聽并更新顯示盏檐,當(dāng)然啦,我的UI肯定不適合大家驶悟,所以這里只是提供一種方法胡野,不是寫的工具能直接拿來(lái)就能使用,demo支持iOS8*痕鳍。下面給出整個(gè)demo的github:
Kegen
demo很粗糙硫豆,但是注釋都有寫,勿直接使用笼呆、熊响、、GPUImage很強(qiáng)大诗赌,還有很多都沒(méi)使用到汗茄,有需要可以研究研究,很實(shí)用铭若。demo中視頻會(huì)在相冊(cè)里存兩份洪碳,一份是壓縮后的,一個(gè)是沒(méi)壓縮的奥喻,這個(gè)是我做測(cè)試的時(shí)候存的偶宫,如果大家在測(cè)試的時(shí)候不需要非迹,這個(gè)可以在調(diào)用的時(shí)候可以去掉保存环鲤,我的調(diào)用代碼:

- (IBAction)onVideoButtonAction:(UIButton *)sender {
    KJVideoAlbumController *ctrl = [[KJVideoAlbumController alloc] init];
    ctrl.kj_minTime = 2.0;
    ctrl.kj_maxTime = 15.0f;
    WS(weakSelf)
    ctrl.kj_complete = ^(NSURL *outPath) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf editViewPath:outPath];
        });
    };
    UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
    navc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentViewController:navc animated:YES completion:nil];
}

- (void)editVideo:(NSString *)localIdentifier {
    WS(weakSelf)
    [KJUtility kj_getAssetForLocalIdentifier:localIdentifier completionHandler:^(PHAsset *kj_object) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [KJUtility kj_requestVideoForAsset:kj_object completion:^(AVURLAsset *asset) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    KJEditVideoViewController *ctrl = [[KJEditVideoViewController alloc] init];
                    ctrl.kj_localVideo = asset;
                    ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
                    UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
                    [weakSelf presentViewController:navc animated:YES completion:nil];
                });
            }];
        });
    }];
}

- (void)editViewPath:(NSURL *)path {
    KJEditVideoViewController *ctrl = [[KJEditVideoViewController alloc] init];
    ctrl.kj_localVideo = path;
    ctrl.kj_isSelectCover = YES;
    WS(weakSelf)
/**
 * videoPath壓縮后的視頻
 * localIdentifier保存到相冊(cè)的高質(zhì)量視頻(如果沒(méi)有添加濾鏡或音樂(lè),返回nil)
 * kj_cover 封面圖
 */
    ctrl.editCompleteBlock = ^(NSURL *videoPath, NSString *localidentifier, UIImage *kj_cover) {
        [weakSelf saveVideoToLibVideoUrl:videoPath];
    };
    ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
    [self presentViewController:navc animated:YES completion:nil];
}

//保存到相冊(cè)
- (void)saveVideoToLibVideoUrl:(NSURL *)url {
    [KJUtility kj_saveVideoToLibraryForPath:url.path completeHandler:^(NSString *localIdentifier, BOOL isSuccess) {
        if (isSuccess) {
            NSLog(@"保存到相冊(cè)成功");
        } else {
            NSLog(@"保存到相冊(cè)失敗");
        }
    }];
}

另外可能大家在看這代碼里面有很多都是調(diào)用KJUtility的代碼憎兽,把h文件放出來(lái)就知道了:

//
//  KJUtility.h
//  KJAlbumDemo
//
//  Created by JOIN iOS on 2017/9/5.
//  Copyright ? 2017年 Kegem. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
#import "UIKit+BaseExtension.h"
#import <YYWebImage.h>
#import <Masonry.h>
#import <YYAnimatedImageView.h>
#import <GPUImage/GPUImage.h>

@protocol KJCustomCameraDelegate <NSObject>

- (void)kj_didStartTakeAction;

- (void)kj_didResetTakeAction;

- (void)kj_didCompleteAction;

- (void)kj_didCancelAction;

@end

@protocol KJVideoFileDelegate <NSObject>

//這里是不保存到相冊(cè)冷离,返回錄制完后的地址
- (void)kj_videoFileCompleteLocalPath:(NSString *)kj_outPath;

@end

//狀態(tài)條的高
#define StatusBarHeight [[UIApplication sharedApplication] statusBarFrame].size.height
//得到屏幕bounds
#define SCREEN_SIZE [UIScreen mainScreen].bounds
//得到屏幕height
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
//得到屏幕width
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
//self
#define WS(weakSelf)  __weak typeof (self) weakSelf = self;
//顏色
#define sYellowColor    0xffd700

@interface KJUtility : NSObject

+ (void)showAllTextDialog:(UIView *)view  Text:(NSString *)text;
+ (void)showProgressDialogText:(NSString *)text;
+ (void)hideProgressDialog;

//傳入 秒  得到  xx分鐘xx秒
+ (NSString *)getMMSSFromSS:(NSInteger)seconds;

/**
  拍攝的圖片/視頻等存放的路徑
  
  @return 文件路徑
  */
+ (NSString *)kj_getKJAlbumFilePath;

/**
 視頻名
 
 @return 返回視頻名
 */
+ (NSString *)kj_getNewFileName;

/**
 根據(jù)PHAsset獲取圖片
 
 @param asset PHAsset
 @param isSynchronous 同步-YES 異步-NO
 @param completion 返回圖片
 */
+ (void)kj_requestImageForAsset:(PHAsset *)asset
             withSynchronous:(BOOL)isSynchronous
                  completion:(void (^)(UIImage *image))completion;

/**
 根據(jù)PHAsset獲取視頻
 
 @param kj_asset PHAsset
 @param completion AVURLAsset
 */
+ (void)kj_requestVideoForAsset:(PHAsset *)kj_asset
                  completion:(void (^)(AVURLAsset *asset))completion;

/**
 獲取視頻的縮略圖方法

 @param urlAsset 視頻的本地路徑
 @param start 開始時(shí)間
 @param timescale scale
 @return 視頻截圖
 */
+ (UIImage *)kj_getScreenShotImageFromVideoPath:(AVURLAsset *)urlAsset
                                   withStart:(CGFloat)start
                               withTimescale:(CGFloat)timescale ;

/**
 圖片保存到系統(tǒng)相冊(cè)
 
 @param image 圖片
 @param completionHandler 返回結(jié)果
 */
+ (void)kj_savePhotoToLibraryForImage:(UIImage *)image
                   completeHandler:(void(^)(NSString *localIdentifier, BOOL isSuccess))completionHandler;

/**
 視頻保存到系統(tǒng)相冊(cè)
 
 @param path 視頻路徑
 @param completionHandler 返回結(jié)果
 */
+ (void)kj_saveVideoToLibraryForPath:(NSString *)path
                  completeHandler:(void(^)(NSString *localIdentifier, BOOL isSuccess))completionHandler;

/**
 根據(jù)相冊(cè)localid獲取PHAsset
 
 @param localIdentifier 相冊(cè)id
 @param completionHandler 返回PHAsset對(duì)象
 */
+ (void)kj_getAssetForLocalIdentifier:(NSString *)localIdentifier
                 completionHandler:(void(^)(PHAsset *kj_object))completionHandler;

/**
 視頻轉(zhuǎn)碼/壓縮
 
 @param asset AVAsset
 @param presetName 視頻質(zhì)量(建議壓縮上傳使用AVAssetExportPresetMediumQuality根據(jù)需求設(shè)置)
 @param savePath 保存的路徑
 @param completeBlock 返回狀態(tài)
 */
+ (void)kj_compressedVideoAsset:(AVAsset *)asset
                 withPresetName:(NSString *)presetName
                withNewSavePath:(NSURL *)savePath
              withCompleteBlock:(void(^)(NSError *error))completeBlock;

/**
 相冊(cè)授權(quán)
 
 @param ctrl 當(dāng)前控制器
 @param completeBlock 返回是否允許訪問(wèn)
 */
+ (void)kj_photoLibraryAuthorizationStatus:(UIViewController *)ctrl
                             completeBlock:(void (^)(BOOL allowAccess))completeBlock;

/**
 相機(jī)授權(quán)
 
 @param ctrl 當(dāng)前控制器
 @param completeBlock 返回是否允許訪問(wèn)
 */
+ (void)kj_cameraAuthorizationStatus:(UIViewController *)ctrl
                       completeBlock:(void (^)(BOOL allowAccess))completeBlock;

/**
 麥克風(fēng)授權(quán)
 
 @param ctrl 當(dāng)前控制器
 @param completeBlock 返回是否允許訪問(wèn)
 */
+ (void)kj_requestRecordPermission:(UIViewController *)ctrl
                     completeBlock:(void (^)(BOOL allowAccess))completeBlock;

/**
 授權(quán)提示彈出框(跳轉(zhuǎn)到手機(jī)設(shè)置-應(yīng)用權(quán)限)
 
 @param ctrl 當(dāng)前控制器
 @param title 提示語(yǔ)
 */
+ (void)kj_authorizationAlert:(UIViewController *)ctrl
                   tipMessage:(NSString *)title;

/**
 圖片加濾鏡
 
 @param image 源圖
 @param filterName 濾鏡名字
 @return 合成濾鏡后的圖片
 */
+ (UIImage *)kj_imageProcessedUsingGPUImage:(UIImage *)image
                             withFilterName:(NSString *)filterName;

@end

這里本意是在使用完短視頻的功能后吵冒,刪除沙盒中的kjalbum的文件夾。好了西剥,不在過(guò)多的贅述了痹栖,大家直接下載demo看看就知道了,下面給出效果展示gif瞭空,由于太大揪阿,壓縮處理了,可能有點(diǎn)模糊:

KJlbum.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咆畏,一起剝皮案震驚了整個(gè)濱河市南捂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旧找,老刑警劉巖溺健,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異钮蛛,居然都是意外死亡鞭缭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門魏颓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岭辣,“玉大人,你說(shuō)我怎么就攤上這事甸饱∫捉幔” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵柜候,是天一觀的道長(zhǎng)搞动。 經(jīng)常有香客問(wèn)我,道長(zhǎng)渣刷,這世上最難降的妖魔是什么鹦肿? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮辅柴,結(jié)果婚禮上箩溃,老公的妹妹穿的比我還像新娘。我一直安慰自己碌嘀,他們只是感情好涣旨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著股冗,像睡著了一般霹陡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天烹棉,我揣著相機(jī)與錄音攒霹,去河邊找鬼。 笑死浆洗,一個(gè)胖子當(dāng)著我的面吹牛催束,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伏社,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼抠刺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了摘昌?” 一聲冷哼從身側(cè)響起矫付,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎第焰,沒(méi)想到半個(gè)月后买优,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挺举,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年杀赢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湘纵。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脂崔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梧喷,到底是詐尸還是另有隱情砌左,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布铺敌,位于F島的核電站汇歹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偿凭。R本人自食惡果不足惜产弹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弯囊。 院中可真熱鬧痰哨,春花似錦、人聲如沸匾嘱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)霎烙。三九已至撬讽,卻和暖如春蕊连,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锐秦。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盗忱,地道東北人酱床。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像趟佃,于是被迫代替她去往敵國(guó)和親扇谣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容