iOS 微信小視頻&自定義相機(jī)&GPUImage使用心得

自定義相機(jī)&GPUImage濾鏡錄像

業(yè)務(wù)需求照著微信小視頻做然爆,后來發(fā)現(xiàn)挺有意思的就研究了一下,主要是一些關(guān)鍵性代碼和一些坑的心得體會黍图,具體AVFoundation的使用和UI搭建具體看demo就好曾雕。目前性能方面有些粗糙,交流學(xué)習(xí)使用助被,后期會將相關(guān)優(yōu)化代碼合并進(jìn)來翻默。
相關(guān)功能:
視頻循環(huán)播放,視頻壓縮(80%壓縮率);
錄像時(shí)攝像頭切換的I/O處理;
GPUimage的使用心得與相關(guān)注意事項(xiàng)(圖像旋轉(zhuǎn)恰起、畫面閃爍)

1、微信小視頻錄制(半屏幕)

視頻壓縮:6秒原版視頻10M上下趾牧,壓縮后1.5M上下,具體大小跟像素和拍攝內(nèi)容有關(guān)检盼。
視頻錄制壓縮具體看demo代碼,別的也沒什么難度翘单。
這里放個(gè)進(jìn)度條的伸縮動畫吨枉。

    CABasicAnimation *scaleXAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
    scaleXAnimation.duration = MAXDURATION;
    scaleXAnimation.fromValue = @(1.f);
    scaleXAnimation.toValue = @(0.f);
    [self.processView.layer addAnimation:scaleXAnimation forKey:@"scaleXAnimation"];

2、微信小視頻錄制拍照 (全屏)錄制拍攝后預(yù)覽功能

長按錄像哄芜,點(diǎn)擊拍照貌亭,預(yù)覽確認(rèn)選擇

[前后攝像頭切換]

- (void)swapFrontAndBackCameras {
    NSArray *inputs =self.session.inputs;
    for (AVCaptureDeviceInput *input in inputs ) {
        AVCaptureDevice *device = input.device;
        if ( [device hasMediaType:AVMediaTypeVideo] ) {
            AVCaptureDevicePosition position = device.position;
            if (position ==AVCaptureDevicePositionFront)
                self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionBack];
            else
                self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionFront];
            
                        self.videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:nil];
            
                        [self.session beginConfiguration];
                        [self.session removeInput:input];
                        [self.session addInput:self.videoDeviceInput];
                        [self.session commitConfiguration];
            break;
        }
    }
}

這個(gè)只是達(dá)到了切后前后攝像頭的效果劣挫,但是實(shí)際錄像中切換攝像頭這樣會導(dǎo)致捕捉不到數(shù)據(jù)雳刺,所以在切換的時(shí)候需要重新配置 AVCaptureDevice和AVCaptureDeviceInput堕油,目前處理是切換過后重新配置了session瘪撇。

- (void)swapFrontAndBackCameras {
    NSArray *inputs =self.session.inputs;
    for (AVCaptureDeviceInput *input in inputs ) {
        AVCaptureDevice *device = input.device;
        if ( [device hasMediaType:AVMediaTypeVideo] ) {
            AVCaptureDevicePosition position = device.position;
            if (position ==AVCaptureDevicePositionFront)
                [self configurationSession];
            else
                [self configurationSessionFront];
            break;
        }
    }
}

[手動點(diǎn)擊對焦]

嘗試了很多對焦方式谣光,發(fā)現(xiàn)這種對焦效果最好丁恭。
點(diǎn)擊手勢添加到preview上半醉,根據(jù)點(diǎn)擊的坐標(biāo)進(jìn)行計(jì)算轉(zhuǎn)換point

- (void)tapPreview:(UITapGestureRecognizer *)tap {
    NSLog(@"%@", NSStringFromCGPoint([tap locationInView:self]));
    CGPoint point = [tap locationInView:self];
    [self showFouceView:point];
    CGPoint focusPoint = CGPointMake(point.x/self.width, point.y/self.height);
    [self.recorder setFocusPoint:focusPoint];
 }
- (void)setFocusPoint:(CGPoint)point {
    if (self.captureDevice.isFocusPointOfInterestSupported) {
        NSError *error = nil;
        [self.captureDevice lockForConfiguration:&error];
        /*****必須先設(shè)定聚焦位置隔嫡,在設(shè)定聚焦方式******/
        //聚焦點(diǎn)的位置
        if ([self.captureDevice isFocusPointOfInterestSupported]) {
            [self.captureDevice setFocusPointOfInterest:point];
        }
        
        // 聚焦模式
        if ([self.captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
            [self.captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }else{
            NSLog(@"聚焦模式修改失敗");
        }
        
        //曝光點(diǎn)的位置
        if ([self.captureDevice isExposurePointOfInterestSupported]) {
            [self.captureDevice setExposurePointOfInterest:point];
        }
        
        //曝光模式
        if ([self.captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
            [self.captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }else{
            NSLog(@"曝光模式修改失敗");
        }
        [self.captureDevice unlockForConfiguration];
    }
}

單機(jī)拍照處理涂屁,獲取AVCaptureStillImageOutput類中的 capture方法即可

 [self.recorder.imageDataOutput captureStillImageAsynchronouslyFromConnection:conntion
                                                      completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
                                                          if (imageDataSampleBuffer == nil) {
                                                              return;                                                      }
                                                          NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                                                          UIImage *img = [UIImage imageWithData:imageData];
                                                          [weakSelf previewPhoto:img];
                                                      }];

[錄制 &拍攝完的 錄像預(yù)覽]

這里是在寫了一個(gè)vc中WXVideoPreviewViewController书在,方便后期做濾鏡處理等,拍攝完成后將view添加在錄制view的上層拆又,進(jìn)行本地視頻的循環(huán)播放儒旬。循環(huán)播放結(jié)束時(shí)會出現(xiàn)閃屏的情況栏账,所以這里會將視頻中的第一幀取出進(jìn)行默認(rèn)展示。如有好的方法留言告知我哈栈源。

取本地視頻第一幀畫面

- (UIImage*) getVideoPreViewImage {
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:self.url] options:nil];
    AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    
    gen.appliesPreferredTrackTransform = YES;
    CMTime time = CMTimeMakeWithSeconds(0.0, 600);
    NSError *error = nil;
    CMTime actualTime;
    CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    UIImage *img = [[UIImage alloc] initWithCGImage:image];
    CGImageRelease(image);
    return img;
}

本地視頻播放由AVPlayerItem挡爵、AVPlayer、AVPlaerLayer組成凉翻,所以封住了一個(gè)VideoPlayerView方便使用了讨,其中_playerLayer.videoGravity這個(gè)屬性需要注意到

- (instancetype)initWithFrame:(CGRect)frame videoUrl:(NSString *)url {
    if (self = [super initWithFrame:frame]) {
        _playItem = [AVPlayerItem playerItemWithURL:[self urlValidation:url]];
        _player = [[AVPlayer alloc] initWithPlayerItem:_playItem];
        _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
        _playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; // 不寫這句是不會全屏的!
        _playerLayer.frame = self.bounds;
        [self.layer addSublayer:_playerLayer];
        
        if (kSYSTEM_VERSION_iOS10Later) _player.automaticallyWaitsToMinimizeStalling = NO;
        [self noticeAndKVO];
    }
    return self;
}

視頻重復(fù)播放制轰,只需要在AVPlayerItemDidPlayToEndTimeNotification監(jiān)聽方法中前计,將timer重制即可

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerDidFinished:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:nil];

- (void)playerDidFinished:(NSNotification*)noti {
    [_player pause];
    [self cyclePlayVideo];
}

- (void)cyclePlayVideo {
    [_player seekToTime:kCMTimeZero];
    [_player play];
}

3、GPUImage 視頻錄制與拍照功能

[實(shí)時(shí)添加濾鏡與前后鏡頭切換錄制]

這里有個(gè)不解 _filterView.fillMode 中寫 kGPUImageFillModePreserveAspectRatioAndFill有問題,不太懂什么鬼垃杖,所以這里用2表示男杈。


94C370D1-E565-49A2-8AA4-A975CFEE3CAB.png

camer初始化這里注意,如果出現(xiàn)添加濾鏡閃屏的情況调俘,嘗試[self.camera removeAllTargets] 清除targets伶棒,并查看 addTarget相關(guān)代碼順序。

- (void)captureAndRecording {
    [self addSubview:self.filterView];
    
    [self.camera addTarget:self.filterView];
    [self.camera startCameraCapture];
    self.camera.audioEncodingTarget = self.writer;
}

- (GPUImageView *)filterView {
    if (!_filterView) {
        _filterView = [[GPUImageView alloc] initWithFrame:self.bounds];
        _filterView.fillMode = 2;//kGPUImageFillModePreserveAspectRatioAndFill
    }
    return _filterView;
}

- (GPUImageStillCamera *)camera {
    if (!_camera) {
        self.camera = [[GPUImageStillCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
        self.camera.outputImageOrientation = UIInterfaceOrientationPortrait;
        self.camera.horizontallyMirrorFrontFacingCamera = YES; // 前置攝像頭需要 鏡像反轉(zhuǎn)
        self.camera.horizontallyMirrorRearFacingCamera = NO; // 后置攝像頭不需要 鏡像反轉(zhuǎn) (default:NO)
        [self.camera addAudioInputsAndOutputs]; //該句可防止允許聲音通過的情況下彩库,避免錄制第一幀黑屏閃屏
    }
    return _camera;
}

濾鏡切換, 由于GPUImage 無濾鏡的時(shí)候不支持錄像和拍照肤无,所以正常模式不做處理。沒有濾鏡的時(shí)候如何進(jìn)行錄像和拍照骇钦,如有老司機(jī)知道望告知一下啊宛渐,交流學(xué)習(xí)。
這里GPUImageBeautifyFilter 美顏處理來自guikz 眯搭,感謝github開源精神窥翩。

- (void)filter:(UIButton *)btn {
    FaceCameraFilterEnum filterEnum = btn.tag;
    [self.camera removeAllTargets];
    switch (filterEnum) {
        case FaceCameraFilterNone:{
            btn.tag = 1;
            [self.camera addTarget:_filterView];
            _tempFilter = nil;
            [_beautifyBtn setTitle:@"無" forState:UIControlStateNormal];
        }
            break;
        case FaceCameraFilterBeautify:{
            btn.tag = 2;
            // MARK: 添加 美顏濾鏡
            _beautifyFilter = [[GPUImageBeautifyFilter alloc] init];
            [self.camera addTarget:_beautifyFilter];
            [_beautifyFilter addTarget:_filterView];
            [_beautifyFilter addTarget:self.writer];
            _tempFilter = _beautifyFilter;
            [_beautifyBtn setTitle:@"美顏" forState:UIControlStateNormal];
        }
            break;
        case FaceCameraFilterSketch:{
            btn.tag = 0;
            GPUImageSketchFilter *filter = [[GPUImageSketchFilter alloc] init];
            [self.camera addTarget:filter];
            [filter addTarget:_filterView];
            [filter addTarget:self.writer];
            _tempFilter = filter;
            [_beautifyBtn setTitle:@"黑白" forState:UIControlStateNormal];
        }
            break;
        default:
            break;
    }
}

拍照存儲這里有個(gè)坑,如果使用 writeImageDataToSavedPhotosAlbum 這個(gè)方法進(jìn)行照片存儲,圖片會旋轉(zhuǎn)90度鳞仙,所以寇蚊,這里使用 UIImageWriteToSavedPhotosAlbum進(jìn)行圖片寫入到相冊。

- (void)writerCurrentFrameToLibrary {
    _savingImg = YES;
    kWeakSelf(self)

    if (_tempFilter) {
        #warning 這是第二個(gè)坑棍好,用這種方式保存照片到相冊正常仗岸,官方demo種的相片保存會90度旋轉(zhuǎn)
        //            ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        //            [library writeImageDataToSavedPhotosAlbum:processedJPEG metadata:self.camera.currentCaptureMetadata completionBlock:^(NSURL *assetURL, NSError *error2) {
        //            }];
        [self.camera capturePhotoAsJPEGProcessedUpToFilter:_tempFilter withCompletionHandler:^(NSData *processedJPEG, NSError *error){
            UIImage *img = [UIImage imageWithData:processedJPEG];
            [weakSelf saveImageWriteToPhotosAlbum:img];
        }];
    }else {
         [self showAlterViewTitle:@"失敗" message:@"沒有濾鏡不能進(jìn)行拍照"];
    }
}

- (void)saveImageWriteToPhotosAlbum:(UIImage *)img {
    ALAuthorizationStatus author = [ALAssetsLibrary authorizationStatus];
    if (author == ALAuthorizationStatusRestricted || author == ALAuthorizationStatusDenied){
        //無權(quán)限
        return ;
    }
    if (img) {
        UIImageWriteToSavedPhotosAlbum(img, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
    }else {
         [self showAlterViewTitle:@"失敗" message:@"照片保存失敗"];
    }
}

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    if (!error) {
        [self showAlterViewTitle:@"成功" message:@"照片保存成功"];
    }else {
        [self showAlterViewTitle:@"失敗" message:@"照片保存失敗"];
    }
}

4、本地&在線小視頻播放(略粗糙)

沒什么難度梳玫,感覺稍微有用點(diǎn)代碼是視頻下載這段

- (void)downloadVideo {
    NSString *domainUrl = self.videoUrlString;
    NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:domainUrl] cachePolicy:1 timeoutInterval:15.0f];
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSMutableString * path = [[NSMutableString alloc]initWithString:documentsDirectory];
        NSString *timeString = [NSString stringWithFormat:@"%.0f", [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970]];
        [path appendString:[NSString stringWithFormat:@"/%@.mov", timeString]];
        
        NSLog(@"path == %@", path);
        
        if ([data writeToFile:path atomically:YES])
        {
            NSLog(@"mov寫入成功");
            UIImageWriteToSavedPhotosAlbum([UIImage imageWithContentsOfFile:path], nil, nil, NULL);
            BOOL compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path);
            if (compatible)
            {
                UISaveVideoAtPathToSavedPhotosAlbum(path, self, nil, NULL);
                NSLog(@"視頻保存成功");
            }
            else
            {
                NSLog(@"視頻保存失敗");
            }
        }
        else
        {
            NSLog(@"mov寫入失敗");
        }
    }];
}

截圖

IMG_8039.PNG

IMG_8042.PNG

IMG_8032.PNG

IMG_8035.PNG

IMG_8037.PNG

IMG_8043.PNG

IMG_8044.PNG

第一次寫爹梁,比較倉促,后期想到什么了再補(bǔ)提澎,一起交流學(xué)習(xí)姚垃。

Demo:
https://github.com/MarkBuster/MBSmartVideo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盼忌,隨后出現(xiàn)的幾起案子积糯,更是在濱河造成了極大的恐慌掂墓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件看成,死亡現(xiàn)場離奇詭異君编,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)川慌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門吃嘿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梦重,你說我怎么就攤上這事兑燥。” “怎么了琴拧?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵降瞳,是天一觀的道長。 經(jīng)常有香客問我蚓胸,道長挣饥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任沛膳,我火速辦了婚禮扔枫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锹安。我一直安慰自己茧吊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布八毯。 她就那樣靜靜地躺著,像睡著了一般瞄桨。 火紅的嫁衣襯著肌膚如雪话速。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天芯侥,我揣著相機(jī)與錄音泊交,去河邊找鬼。 笑死柱查,一個(gè)胖子當(dāng)著我的面吹牛廓俭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唉工,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼研乒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淋硝?” 一聲冷哼從身側(cè)響起雹熬,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宽菜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后竿报,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铅乡,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年烈菌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阵幸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芽世,死狀恐怖挚赊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捂襟,我是刑警寧澤咬腕,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站葬荷,受9級特大地震影響涨共,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宠漩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一举反、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扒吁,春花似錦火鼻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盼铁,卻和暖如春粗蔚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饶火。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工鹏控, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肤寝。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓当辐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲤看。 傳聞我的和親對象是個(gè)殘疾皇子缘揪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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