gpt4 book ai didi

手把手教你实现微信小视频iOS代码实现

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章手把手教你实现微信小视频iOS代码实现由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

前段时间项目要求需要在聊天模块中加入类似微信的小视频功能,这边博客主要是为了总结遇到的问题和解决方法,希望能够对有同样需求的朋友有所帮助.

效果预览:

  。

这里先罗列遇到的主要问题:    1.视频剪裁  微信的小视频只是取了摄像头获取的一部分画面  2.滚动预览的卡顿问题  AVPlayer播放视频在滚动中会出现很卡的问题 。

接下来让我们一步步来实现。 Part 1 实现视频录制 1.录制类WKMovieRecorder实现 创建一个录制类WKMovieRecorder,负责视频录制。  。

?
1
2
3
4
5
@interface WKMovieRecorder : NSObject
 
+ (WKMovieRecorder*) sharedRecorder;
  - (instancetype)initWithMaxDuration:(NSTimeInterval)duration;
  @end

定义回调block  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
  * 录制结束
  *
  * @param info   回调信息
  * @param isCancle YES:取消 NO:正常结束
  */
typedef void (^FinishRecordingBlock)(NSDictionary *info, WKRecorderFinishedReason finishReason);
/**
  * 焦点改变
  */
typedef void (^FocusAreaDidChanged)();
/**
  * 权限验证
  *
  * @param success 是否成功
  */
typedef void (^AuthorizationResult)( BOOL success);
 
@interface WKMovieRecorder : NSObject
//回调
@property (nonatomic, copy) FinishRecordingBlock finishBlock; //录制结束回调
@property (nonatomic, copy) FocusAreaDidChanged focusAreaDidChangedBlock;
@property (nonatomic, copy) AuthorizationResult authorizationResultBlock;
@end

定义一个cropSize用于视频裁剪  @property (nonatomic, assign) CGSize cropSize,

接下来就是capture的实现了,这里代码有点长,懒得看的可以直接看后面的视频剪裁部分 。

录制配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@interface WKMovieRecorder ()
<
AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureAudioDataOutputSampleBufferDelegate,
WKMovieWriterDelegate
>
 
{
   AVCaptureSession* _session;
   AVCaptureVideoPreviewLayer* _preview;
   WKMovieWriter* _writer;
   //暂停录制
   BOOL _isCapturing;
   BOOL _isPaused;
   BOOL _discont;
   int _currentFile;
   CMTime _timeOffset;
   CMTime _lastVideo;
   CMTime _lastAudio;
  
   NSTimeInterval _maxDuration;
}
 
// Session management.
@property (nonatomic, strong) dispatch_queue_t sessionQueue;
@property (nonatomic, strong) dispatch_queue_t videoDataOutputQueue;
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureDevice *captureDevice;
@property (nonatomic, strong) AVCaptureDeviceInput *videoDeviceInput;
@property (nonatomic, strong) AVCaptureStillImageOutput *stillImageOutput;
@property (nonatomic, strong) AVCaptureConnection *videoConnection;
@property (nonatomic, strong) AVCaptureConnection *audioConnection;
@property (nonatomic, strong) NSDictionary *videoCompressionSettings;
@property (nonatomic, strong) NSDictionary *audioCompressionSettings;
@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *adaptor;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;
 
 
//Utilities
@property (nonatomic, strong) NSMutableArray *frames; //存储录制帧
@property (nonatomic, assign) CaptureAVSetupResult result;
@property (atomic, readwrite) BOOL isCapturing;
@property (atomic, readwrite) BOOL isPaused;
@property (nonatomic, strong) NSTimer *durationTimer;
 
@property (nonatomic, assign) WKRecorderFinishedReason finishReason;
 
@end

实例化方法:  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
+ (WKMovieRecorder *)sharedRecorder
{
   static WKMovieRecorder *recorder;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
     recorder = [[WKMovieRecorder alloc] initWithMaxDuration:CGFLOAT_MAX];
   });
  
   return recorder;
}
 
- (instancetype)initWithMaxDuration:(NSTimeInterval)duration
{
   if (self = [self init]){
     _maxDuration = duration;
     _duration = 0.f;
   }
  
   return self;
}
 
- (instancetype)init
{
   self = [super init];
   if (self) {
     _maxDuration = CGFLOAT_MAX;
     _duration = 0.f;
     _sessionQueue = dispatch_queue_create( "wukong.movieRecorder.queue" , DISPATCH_QUEUE_SERIAL );
     _videoDataOutputQueue = dispatch_queue_create( "wukong.movieRecorder.video" , DISPATCH_QUEUE_SERIAL );
     dispatch_set_target_queue( _videoDataOutputQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
   }
   return self;
}

2.初始化设置 初始化设置分别为session创建、权限检查以及session配置 1).session创建 self.session = [[AVCaptureSession alloc] init]; self.result = CaptureAVSetupResultSuccess,

2).权限检查 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//权限检查
     switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
       case AVAuthorizationStatusNotDetermined: {
         [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted) {
           if (granted) {
             self.result = CaptureAVSetupResultSuccess;
           }
         }];
         break ;
       }
       case AVAuthorizationStatusAuthorized: {
        
         break ;
       }
       default :{
         self.result = CaptureAVSetupResultCameraNotAuthorized;
       }
     }
    
     if ( self.result != CaptureAVSetupResultSuccess) {
      
       if (self.authorizationResultBlock) {
         self.authorizationResultBlock(NO);
       }
       return ;
     }
        

3).session配置 session配置是需要注意的是AVCaptureSession的配置不能在主线程, 需要自行创建串行线程。  3.1.1 获取输入设备与输入流 。

?
1
2
3
4
5
6
7
8
9
AVCaptureDevice *captureDevice = [[self class ] deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];     
  _captureDevice = captureDevice;
      
  NSError *error = nil;
  _videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
      
  if (!_videoDeviceInput) {
   NSLog(@ "未找到设备" );
  }

3.1.2 录制帧数设置 帧数设置的主要目的是适配iPhone4,毕竟是应该淘汰的机器了 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int frameRate;
       if ( [NSProcessInfo processInfo].processorCount == 1 )
       {
         if ([self.session canSetSessionPreset:AVCaptureSessionPresetLow]) {
           [self.session setSessionPreset:AVCaptureSessionPresetLow];
         }
         frameRate = 10;
       } else {
         if ([self.session canSetSessionPreset:AVCaptureSessionPreset640x480]) {
           [self.session setSessionPreset:AVCaptureSessionPreset640x480];
         }
         frameRate = 30;
       }
      
       CMTime frameDuration = CMTimeMake( 1, frameRate );
      
       if ( [_captureDevice lockForConfiguration:&error] ) {
         _captureDevice.activeVideoMaxFrameDuration = frameDuration;
         _captureDevice.activeVideoMinFrameDuration = frameDuration;
         [_captureDevice unlockForConfiguration];
       }
       else {
         NSLog( @ "videoDevice lockForConfiguration returned error %@" , error );
       }

3.1.3 视频输出设置 视频输出设置需要注意的问题是:要设置videoConnection的方向,这样才能保证设备旋转时的显示正常。  。

  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//Video
      if ([self.session canAddInput:_videoDeviceInput]) {
       
        [self.session addInput:_videoDeviceInput];
        self.videoDeviceInput = _videoDeviceInput;
        [self.session removeOutput:_videoDataOutput];
       
        AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
        _videoDataOutput = videoOutput;
        videoOutput.videoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
       
        [videoOutput setSampleBufferDelegate:self queue:_videoDataOutputQueue];
       
        videoOutput.alwaysDiscardsLateVideoFrames = NO;
       
        if ( [_session canAddOutput:videoOutput] ) {
          [_session addOutput:videoOutput];
         
          [_captureDevice addObserver:self forKeyPath:@ "adjustingFocus" options:NSKeyValueObservingOptionNew context:FocusAreaChangedContext];
         
          _videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
         
          if (_videoConnection.isVideoStabilizationSupported){
            _videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
          }
 
         
          UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
          AVCaptureVideoOrientation initialVideoOrientation = AVCaptureVideoOrientationPortrait;
          if ( statusBarOrientation != UIInterfaceOrientationUnknown ) {
            initialVideoOrientation = (AVCaptureVideoOrientation)statusBarOrientation;
          }
         
          _videoConnection.videoOrientation = initialVideoOrientation;
        }
 
      }
      else {
        NSLog(@ "无法添加视频输入到会话" );
      }

3.1.4 音频设置  需要注意的是为了不丢帧,需要把音频输出的回调队列放在串行队列中  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//audio
       AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
       AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
      
      
       if ( ! audioDeviceInput ) {
         NSLog( @ "Could not create audio device input: %@" , error );
       }
      
       if ( [self.session canAddInput:audioDeviceInput] ) {
         [self.session addInput:audioDeviceInput];
        
       }
       else {
         NSLog( @ "Could not add audio device input to the session" );
       }
      
       AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init];
       // Put audio on its own queue to ensure that our video processing doesn't cause us to drop audio
       dispatch_queue_t audioCaptureQueue = dispatch_queue_create( "wukong.movieRecorder.audio" , DISPATCH_QUEUE_SERIAL );
       [audioOut setSampleBufferDelegate:self queue:audioCaptureQueue];
      
       if ( [self.session canAddOutput:audioOut] ) {
         [self.session addOutput:audioOut];
       }
       _audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];

还需要注意一个问题就是对于session的配置代码应该是这样的  [self.session beginConfiguration],

...配置代码 。

[self.session commitConfiguration],

由于篇幅问题,后面的录制代码我就挑重点的讲了。 3.2  视频存储 现在我们需要在AVCaptureVideoDataOutputSampleBufferDelegate与AVCaptureAudioDataOutputSampleBufferDelegate的回调中,将音频和视频写入沙盒。在这个过程中需要注意的,在启动session后获取到的第一帧黑色的,需要放弃。 3.2.1 创建WKMovieWriter类来封装视频存储操作 WKMovieWriter的主要作用是利用AVAssetWriter拿到CMSampleBufferRef,剪裁后再写入到沙盒中。 这是剪裁配置的代码,AVAssetWriter会根据cropSize来剪裁视频,这里需要注意的一个问题是cropSize的width必须是320的整数倍,不然的话剪裁出来的视频右侧会出现一条绿色的线 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
NSDictionary *videoSettings;
  if (_cropSize.height == 0 || _cropSize.width == 0) {
   
    _cropSize = [UIScreen mainScreen].bounds.size;
   
  }
 
  videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
           AVVideoCodecH264, AVVideoCodecKey,
           [NSNumber numberWithInt:_cropSize.width], AVVideoWidthKey,
           [NSNumber numberWithInt:_cropSize.height], AVVideoHeightKey,
           AVVideoScalingModeResizeAspectFill,AVVideoScalingModeKey,
           nil];

至此,视频录制就完成了。 接下来需要解决的预览的问题了  。

Part 2 卡顿问题解决 1.1 gif图生成  通过查资料发现了这篇blog 介绍说微信团队解决预览卡顿的问题使用的是播放图片gif,但是博客中的示例代码有问题,通过CoreAnimation来播放图片导致内存暴涨而crash。但是,还是给了我一些灵感,因为之前项目的启动页用到了gif图片的播放,所以我就想能不能把视频转成图片,然后再转成gif图进行播放,这样不就解决了问题了吗。于是我开始google功夫不负有心人找到了,图片数组转gif图片的方法.

gif图转换代码  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void makeAnimatedGif(NSArray *images, NSURL *gifURL, NSTimeInterval duration) {
   NSTimeInterval perSecond = duration /images.count;
  
   NSDictionary *fileProperties = @{
                    (__bridge id)kCGImagePropertyGIFDictionary: @{
                        (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
                        }
                    };
  
   NSDictionary *frameProperties = @{
                    (__bridge id)kCGImagePropertyGIFDictionary: @{
                        (__bridge id)kCGImagePropertyGIFDelayTime: @(perSecond), // a float (not double!) in seconds, rounded to centiseconds in the GIF data
                        }
                    };
  
   CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)gifURL, kUTTypeGIF, images.count, NULL);
   CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
  
   for (UIImage *image in images) {
     @autoreleasepool {
      
       CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
     }
   }
  
   if (!CGImageDestinationFinalize(destination)) {
     NSLog(@ "failed to finalize image destination" );
   } else {
    
    
   }
   CFRelease(destination);
}

转换是转换成功了,但是出现了新的问题,使用ImageIO生成gif图片时会导致内存暴涨,瞬间涨到100M以上,如果多个gif图同时生成的话一样会crash掉,为了解决这个问题需要用一个串行队列来进行gif图的生成    。

1.2 视频转换为UIImages 主要是通过AVAssetReader、AVAssetTrack、AVAssetReaderTrackOutput 来进行转换  。

  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//转成UIImage
- ( void )convertVideoUIImagesWithURL:(NSURL *)url finishBlock:( void (^)(id images, NSTimeInterval duration))finishBlock
{
     AVAsset *asset = [AVAsset assetWithURL:url];
     NSError *error = nil;
     self.reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
    
     NSTimeInterval duration = CMTimeGetSeconds(asset.duration);
     __weak typeof(self)weakSelf = self;
     dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
     dispatch_async(backgroundQueue, ^{
       __strong typeof(weakSelf) strongSelf = weakSelf;
       NSLog(@ "" );
      
      
       if (error) {
         NSLog(@ "%@" , [error localizedDescription]);
        
       }
      
       NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
      
       AVAssetTrack *videoTrack =[videoTracks firstObject];
       if (!videoTrack) {
         return ;
       }
       int m_pixelFormatType;
       //   视频播放时,
       m_pixelFormatType = kCVPixelFormatType_32BGRA;
       // 其他用途,如视频压缩
       //  m_pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
      
       NSMutableDictionary *options = [NSMutableDictionary dictionary];
       [options setObject:@(m_pixelFormatType) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
       AVAssetReaderTrackOutput *videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:options];
      
       if ([strongSelf.reader canAddOutput:videoReaderOutput]) {
        
         [strongSelf.reader addOutput:videoReaderOutput];
       }
       [strongSelf.reader startReading];
      
      
       NSMutableArray *images = [NSMutableArray array];
       // 要确保nominalFrameRate>0,之前出现过android拍的0帧视频
       while ([strongSelf.reader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {
          @autoreleasepool {
         // 读取 video sample
         CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
        
         if (!videoBuffer) {
           break ;
         }
        
         [images addObject:[WKVideoConverter convertSampleBufferRefToUIImage:videoBuffer]];
        
         CFRelease(videoBuffer);
       }
      
      
      }
       if (finishBlock) {
         dispatch_async(dispatch_get_main_queue(), ^{
           finishBlock(images, duration);
         });
       }
     });
 
 
}

在这里有一个值得注意的问题,在视频转image的过程中,由于转换时间很短,在短时间内videoBuffer不能够及时得到释放,在多个视频同时转换时任然会出现内存问题,这个时候就需要用autoreleasepool来实现及时释放  。

?
1
2
3
4
5
6
7
8
9
@autoreleasepool {
  // 读取 video sample
  CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
     if (!videoBuffer) {
    break ;
    }
          
    [images addObject:[WKVideoConverter convertSampleBufferRefToUIImage:videoBuffer]];
     CFRelease(videoBuffer); }

至此,微信小视频的难点(我认为的)就解决了,至于其他的实现代码请看demo就基本实现了,demo可以从这里下载.

视频暂停录制 http://www.gdcl.co.uk/2013/02/20/iPhone-Pause.html 视频crop绿边解决 http://stackoverflow.com/questions/22883525/avassetexportsession-giving-me-a-green-border-on-right-and-bottom-of-output-vide 视频裁剪:http://stackoverflow.com/questions/15737781/video-capture-with-11-aspect-ratio-in-ios/16910263#16910263 CMSampleBufferRef转image https://developer.apple.com/library/ios/qa/qa1702/_index.html 微信小视频分析 http://www.jianshu.com/p/3d5ccbde0de1 。

感谢以上文章的作者 。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我.

原文链接:http://www.cnblogs.com/pretty-guy/p/5811290.html 。

最后此篇关于手把手教你实现微信小视频iOS代码实现的文章就讲到这里了,如果你想了解更多关于手把手教你实现微信小视频iOS代码实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com