- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我对此很陌生,我在这里的论坛上检查过答案,但我没有找到任何真正可以帮助我的答案。我正在尝试播放 res/raw 文件夹中的视频。到目前为止我已经设置了这段代码: MediaPlayer mp; @Ov
我可以播放一个视频剪辑,检测视频的结尾,然后创建一个表单,然后播放另一个视频剪辑。我的问题是,表单 react 不正确,我创建了带有提交按钮和两个单选按钮可供选择的表单。我希望让用户进行选择,验证响应
首先,我必须说我在web2py讨论组中看到过类似的内容,但我不太理解。 我使用 web2py 设置了一个数据库驱动的网站,其中的条目只是 HTML 文本。其中大多数将包含 img和/或video指向相
我正在尝试在视频 View 中播放 YouTube 视频。 我将 xml 布局如下: 代码是这样的: setContentView(R.layout.webview); VideoV
我正在开发一个需要嵌入其中的 youtube 视频播放器的 android 应用程序。我成功地从 API 获得了 RTSP 视频 URL,但是当我试图在我的 android 视频 View 中加载这个
我目前正在从事一个使用 YouTube API 的网络项目。 我完全不熟悉 API。所以每一行代码都需要付出很多努力。 使用以下代码,我可以成功检索播放列表中的项目: https://www.goog
是否可以仅使用视频 ID 和 key 使用 API V3 删除 youtube 视频?我不断收到有关“必需参数:部分”丢失的错误消息。我用服务器和浏览器 api 键试了一下这是我的代码: // $yo
所以我一直坚持这个大约一个小时左右,我就是无法让它工作。到目前为止,我一直在尝试从字符串中提取整个链接,但现在我觉得只获取视频 ID 可能更容易。 RegEx 需要从以下链接样式中获取 ID/URL,
var app = angular.module('speakout', []).config( function($sceDelegateProvider) {
我正在努力从 RSS 提要中阅读音频、视频新闻。我如何确定该 rss 是用于新闻阅读器还是用于音频或视频? 这是视频源:http://feeds.cbsnews.com/CBSNewsVideo 这是
利用python反转图片/视频 准备:一张图片/一段视频 python库:pillow,moviepy 安装库 ?
我希望在用户双击视频区域时让我的视频全屏显示,而不仅仅是在他们单击控件中的小图标时。有没有办法添加事件或其他东西来控制用户点击视频时发生的情况? 谢谢! 最佳答案 按照 Musa 的建议,附
关闭。这个问题需要更多 focused .它目前不接受答案。 想改进这个问题?更新问题,使其仅关注一个问题 editing this post . 7年前关闭。 Improve this questi
我有一个公司培训视频加载到本地服务器上。我正在使用 HTML5 的视频播放来观看这些视频。该服务器无法访问网络,但我已加载 apache 并且端口 8080 对同一网络上的所有机器开放。 这些文件位于
我想混合来自 video.mp4 的视频(时长 1 分钟)和来自 audio.mp3 的音频(10 分钟持续时间)到一个持续时间为 1 分钟的输出文件中。来自 audio.mp3 的音频应该是从 4
关闭。这个问题需要更多 focused .它目前不接受答案。 想改进这个问题?更新问题,使其仅关注一个问题 editing this post . 8年前关闭。 Improve this questi
我正在尝试使用 peer/getUserMedia 创建一个视频 session 网络应用程序。 目前,当我将唯一 ID 发送到视频 session 时,我能够听到/看到任何加入我的 session
考虑到一段时间内的观看次数,我正在评估一种针对半自动脚本的不同方法,该脚本将对视频元数据执行操作。 简而言之,只要视频达到指标中的某个阈值,就说观看次数,它将触发某些操作。 现在要执行此操作,我将不得
我正在通过iBooks创建专门为iPad创建动态ePub电子书的网站。 它需要支持youtube视频播放,所以当我知道视频的直接路径时,我正在使用html5 标记。 有没有一种使用html5 标签嵌入
我对Android不熟悉,我想浏览youtube.com并在Webview内从网站显示视频。当前,当我尝试执行此操作时,将出现设备的浏览器,并让我使用设备浏览器浏览该站点。如果Webview不具备这种
我是一名优秀的程序员,十分优秀!