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

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

效果预览:

 

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

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

@interface WKMovieRecorder : NSObject

+ (WKMovieRecorder*) sharedRecorder;
 - (instancetype)initWithMaxDuration:(NSTimeInterval)duration;
 @end 

定义回调block 

/**
 * 录制结束
 *
 * @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的实现了,这里代码有点长,懒得看的可以直接看后面的视频剪裁部分

录制配置:

@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

实例化方法: 

+ (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).权限检查

//权限检查
    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 获取输入设备与输入流

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,毕竟是应该淘汰的机器了

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的方向,这样才能保证设备旋转时的显示正常。 

 //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 音频设置 
需要注意的是为了不丢帧,需要把音频输出的回调队列放在串行队列中 

//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的整数倍,不然的话剪裁出来的视频右侧会出现一条绿色的线

 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图转换代码 

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 来进行转换 

//转成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来实现及时释放 

@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

感谢以上文章的作者

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索ios
, 微信
小视频
ios登录注册实现代码、ios闹钟实现代码、ios计步器代码实现、ios 代码实现键盘横屏、ios 代码实现静音,以便于您获取更多的相关知识。

时间: 2024-10-24 11:16:00

手把手教你实现微信小视频iOS代码实现_IOS的相关文章

ios设备突破微信小视频6S限制的方法

刷微信朋友圈只发文字和图片怎能意犹未竟,微信小视频是一个很好的补充,音视频到位,流行流行最流行.但小视频时长不能超过6S,没有滤镜等是很大的遗憾.but有人突破限制玩出了花样,用ios设备在朋友圈晒出超时长.带滤镜甚至慢镜头拍摄的小视频.随ytkah一起看看他们是怎么玩的吧 未越狱ios设备在微信朋友圈上传延时.慢动作.滤镜.超时长小视频的方法: 第①步,将iPhone拍摄好的延时.超时长或者慢动作的视频保存到本地电脑,并同时截取一张视频画面保存.(PS:添加滤镜效果可以通过iMovie等App

微信小视频黑屏怎么样

  现在还是有一些微信用户会遇到微信小视频不能拍的困恼.如小米手机,华为手机,魅族等.微信小视频不能拍和小视频黑屏的原因往往是摄像头权限没打开造成的.这里小编以华为手机作为说明,如果更改权限解决微信小视频黑屏. 一.打开"手机管家".(部分手机可能要在设置里找到权限) 二.打开微信后,按照下图设置权限.(主要是打开摄像头权限) 直接选择"信任此应用"是最方便的. 至此,微信小视频不能拍或者是黑屏的原因,就能解决了.

360手机助手导出微信小视频图文教程

  微信小视频出来有很久了,但因为微信自身的设定,微信小视频在微信聊天里面是不能保存和不能发朋友圈的,而朋友圈的小视频同样也不能保存.遇到非常想要小视频也只能干着急.下面介绍一个非常实用,并且百分百可以导出小视频的教程. 工具需要:连接电脑的手机数据线. 软件支持:360手机助手电脑版 操作步骤: 一:将手机连接电脑. 二:连接成功之后,打开360手机助手,选择'我的手机'栏. 三:找到'我的文件'-'视频' 四:选择要保存的视频,发送到电脑端. 五:操作成功.

各位大神,急需微信小视频Android源码

问题描述 各位大神,急需微信小视频Android源码 目前在做一个社交聊天的软件,其中有部分功能涉及到微信里的小视频功能,在网上没有查到相关的资料,想求各位大神们分享一下小视频的源码,金币木有啦,祝您和家人们新年快乐,万事如意! 解决方案 试试vitamio? 解决方案二: vitamio出了一个方视频recorder的demo 应该最满足你的需求的 解决方案三: http://download.csdn.net/detail/myactivity/9149695 我做的一个小demo,希望能够

微信小视频背后的“大生意”

文/郭静 仅仅只是一个移动端的纯聊天工具,是很难产生巨大粘性的,只要有竞品出现,用户很容易就会被吸引过去,当然,足够的用户量级也是移动社交产品的核心竞争力.从用户的角度来看,纯社交属性也太过于单一,所以微信之上有朋友圈.微信公众平台.社交小游戏等,易信之上有拼车族.问一问.偶遇等功能. 微信6.0版本推出了朋友圈的小视频功能,这是继微信聊天功能加入视频介质.微信公众号加入视频介质之后,视频介质融入到朋友圈功能当中,用户可以在朋友圈发布视频.图片.文字三个介质的内容. 很明显微信选择在国庆节之前发

微信小视频保存在哪里?哪些视频可以保存?

一.微信的小视频哪些可以保存? 1.在我们拍微信小视频后,勾选"保存给自己 和 发给好友"."包括好友发来的" 这些都会保存在你手机中. 2.分享到朋友圈的小视频是不会保存在手机中的. 3.好友朋友圈小视频也不会保存在你手机中的. 二.微信小视频保存在哪里? 保存路径:"sdcard/Tencent/MicroMsg/最长后缀名称文件夹/video"中查找了,具体给各位演示一下 1.在手机的"SD存储卡"内,也可能在 手机存储

微信小视频导出保存方法分享

给各位微信软件的用户们来详细的解析分享一下微信小视频导出保存的方法. 方法分享: 对于Android用户来说,我们可以找到"SD卡→tencent→MicroMsg",然后找到名字最长的那个文件夹,视频都存在了里面一个名叫"video"的文件夹里.   但要是iPhone用户的话,就需要动用一些PC管理软件了,比较麻烦.现在有道云笔记推出了一项新功能,只要把视频发送给其微信公众号,视频便自动存储在自己的云笔记里面了. 操作步骤也比较简单,长按朋友圈视频"收

微信小视频怎么用 微信小视频使用方法详解

给各位微信软件的用户们来详细的解析分享一下微信小视频使用的方法. 方法分享: 1.更新到最新的微信6.0版本(预计6.0版本以上都会保持这个功能),进入到聊天界面,然后点击右边的加号按钮,就能看到那个眼睛的图标,就是小视频了.   2.这里如果要放大的话,不是两个手两边划,而是双击就可以了.   3.聚焦和手机相机一样,点击即可.然后可以按住下面拍摄了.   4.拍摄后就分享咯,没有什么可以取消的按钮,拍了就直接分享了,所以拍摄的时候要注意一点.你的小伙伴点击就可以观看你拍摄的内容.   好了,

微信小视频突破6秒方法详解

给各位微信软件的使用者们来详细的解析分享一下微信小视频突破6秒的方法. 方法分享: 微信小视频,是最新版本推出的一个功能,可以录制6秒视频并上传至朋友圈.但6秒视频太短,有个方法可以解除微信小视频6秒的限制,不过提前是iPhone需要越狱成功.   搜"微信无限小视频",安装之即可.   安装有360.腾讯管家的,请自觉将支付保护关闭,否则无法生效! 发到朋友圈第一次播放可能只有6秒,但是重播就是正常时间长度了!   还有一个方法: 主界面下拉后点录制按钮,只要不放手,就不会停! 在对