扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
笔者iOS开发工程师,现在很多应用场景下都会用到视频播放技术,当然iOS APP也不例外,这是写这篇文章的背景。
十多年的曹妃甸网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销的优势是能够根据用户设备显示端的尺寸不同,自动调整曹妃甸建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“曹妃甸网站设计”,“曹妃甸网站推广”以来,每个客户项目都认真落实执行。
最近我一个同样做iOS工程师的同学说他最近面试了一个人,简历里写着做过视频播放,就问他基本的视频播放原理是什么,结果此人一脸懵逼状什么也没答上来,只是说会用iOS SDK下调用视频播放的几个API。我同学跟我说他就是想问问此人计算机基础怎么样,其实只要应聘者能说出“解码”两个字都会让他满意的,可惜他却什么也没说出来。这又让笔者想起几年前临近大学毕业时同寝室友校招面试时也遇到了同样的问题,他也做过视频播放,人家就问他其中有两个问题,一是如果现在有一种非常奇怪的格式的视频让你播放你该怎么办,他也是懵逼了。二是如果现在有一个1G的超大视频让你播放,你又该怎么办,他直接就傻了,面试自然折戟沉沙了。
事实上仅就iOS APP来讲,要想做出视频播放的功能来,的确是调用 AVFoundation 框架下几个常用的API就可以了,iOS 9 之前常用的是 MPMoviePlayerController ,ios 9 之后推荐使用流播放技术的 AVPlayer ,可是就像常说的API普通的程序员都会调,可是真正有专业基础和想进阶就要知其然还要知其所以然,有专业基础的和半路出家的程序员的最大区别也正在于此,这也就成了本片文章写作的契机,下面这些内容也是笔者整合了一些自己的一些积累,主要来源于我们有合作关系的一家叫保利威视的视频解决方案提供商,希望对大家有帮助,水平有限,有不足之处还请大家不吝赐教。
视音频技术主要包含以下几点:** 封装技术,视频压缩编码技术以及音频压缩编码技术。如果考虑到网络传输的话,还包括流媒体协议技术。**
视频播放器播放网上的视频文件,需要经过以下几个步骤:** 解协议,解封装,解码视音频,视音频同步**。如果播放本地文件则不需要解协议,为以下几个步骤:解封装,解码视音频,视音频同步。过程如图所示。
解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如HTTP,RTMP,或是MMS等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。例如,采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。
解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如MP4,MKV,RMVB,TS,FLV,AVI等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.264,MPEG2,VC-1等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频 数据输出成为非压缩的音频抽样数据,例如PCM数据。
视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡和声卡播放出来。
iOS上除了一些第三方的播放器之外,我们一般常用的播放方式有使用:
一般简单的播放url可以使用网页播放的模式,有很多主流app的视频打开都是用的这种。不需要UI自定义的时候我们选择AVPlayerViewController,比较方便快捷。自定义需求较多时最好选用AVPlayer。
功能最全、自定义最高的播放器,也是使用最多得。使用起来较为复杂些。需导入AVKit控件
AVPlayer功能比较多,另外写了一篇文章:
AVPlayer播放器
只是让视频播放起来,没有暂停、停止、快进等等功能。
适用于简单的播放,不需要过多自定义的东西,使用比较简单,有两种方式,需要引入AVKit框架
(1)添加view
可以设置播放器的大小
(2)作为视图控制器弹窗
使用起来类似AVPlayerViewController的第一种方式。需要引入MediaPlayer框架(iOS9后被抛弃,使用AVPlayerViewController即可)
通知来进行一些操作的监听
使用起来类似AVPlayerViewController的第二种方式。需要引入MediaPlayer框架(iOS9后被抛弃,使用AVPlayerViewController即可)
swift视频播放器使用
最近工作之余, 写了一个视频播放器,输入要播放的视频资源地址,即可实现播放功能。
目前功能比较简单,支持锁屏、屏幕旋转等基础功能,后续会继续完善。。。
下面讲解下实现思路:
因 MediaPlayer/MediaPlayer.h 中 MPMoviePlayerController MP_DEPRECATED("Use AVPlayerViewController in AVKit.", ios(2.0, 9.0)) 在iOS9以后已经废弃,为了更好的兼容性,采用了 AVFoundation/AVFoundation.h 为技术实现方案。
框架在此就不讲解了,官网讲的比较清楚,不懂的可以查看官档。 - 戳这里
监听视频资源的加载状态, 根据不同的状态进行相应的操作。
更新视频资源的播放进度。
实现控制视图 QYPlayerControlView 的Delegate。 当控制视图进行了相应操作,事件被传递到该类中进行统一处理。
该类中还有对操作视图 显示/隐藏 的方法。
后续调整视频亮度、音量、进度等手势都需添加在该分类中,便于统一管理。
主要存放时间转换的分类 NSString+Custom 、常用宏 QYPlayerDefine 等工具类。
1). BaseViewController 中实现了控制屏幕旋转的系统方法,实现的控制器要继承自 BaseViewController 。
2). 在子控制器中实现如下方法。
此时屏幕旋转功能已经添加成功!
以上便是整个播放器的源码解析,具体的细节请查看源码。
源码放在GitHub上了,想查看的小伙伴可以 -戳这里。
千里之行,始于足下。
我们在项目中会遇到播放音频的功能,自己也研究了一下,搞了一个小的功能播放器,供大家交流。brbr我们用之前应该导入mediaolayer的框架。brbr
// MoviePlayerViewController.m
// Player
//
// Created by dllo on 15/11/7.
// Copyright © 2015年 zhaoqingwen. All rights reserved.
//
#import "MoviePlayerViewController.h"
#import AVFoundation/AVFoundation.h
#import MediaPlayer/MediaPlayer.h
@interface MoviePlayerViewController ()
@property(nonatomic,strong)AVPlayer *player; // 播放属性
@property(nonatomic,strong)AVPlayerItem *playerItem; // 播放属性
@property(nonatomic,assign)CGFloat width; // 坐标
@property(nonatomic,assign)CGFloat height; // 坐标
@property(nonatomic,strong)UISlider *slider; // 进度条
@property(nonatomic,strong)UILabel *currentTimeLabel; // 当前播放时间
@property(nonatomic,strong)UILabel *systemTimeLabel; // 系统时间
@property(nonatomic,strong)UIView *backView; // 上面一层Viewd
@property(nonatomic,assign)CGPoint startPoint;
@property(nonatomic,assign)CGFloat systemVolume;
@property(nonatomic,strong)UISlider *volumeViewSlider;
@property(nonatomic,strong)UIActivityIndicatorView *activity; // 系统菊花
@property(nonatomic,strong)UIProgressView *progress; // 缓冲条
@property(nonatomic,strong)UIView *topView;
@end
@implementation MoviePlayerViewController
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
_width = [[UIScreen mainScreen]bounds].size.height;
_height = [[UIScreen mainScreen]bounds].size.width;
// 创建AVPlayer
self.playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@""]];
self.player = [AVPlayer playerWithPlayerItem:_playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
playerLayer.frame = CGRectMake(0, 0, _width, _height);
playerLayer.videoGravity = AVLayerVideoGravityResize;
[self.view.layer addSublayer:playerLayer];
[_player play];
//AVPlayer播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
self.backView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, _width, _height)];
[self.view addSubview:_backView];
_backView.backgroundColor = [UIColor clearColor];
self.topView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, _width, _height * 0.15)];
_topView.backgroundColor = [UIColor blackColor];
_topView.alpha = 0.5;
[_backView addSubview:_topView];
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];// 监听loadedTimeRanges属性
[self createProgress];
[self createSlider];
[self createCurrentTimeLabel];
[self createButton];
[self backButton];
[self createTitle];
[self createGesture];
[self customVideoSlider];
self.activity = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
_activity.center = _backView.center;
[self.view addSubview:_activity];
[_activity startAnimating];
// //延迟线程
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.5 animations:^{
_backView.alpha = 0;
}];
});
//计时器
[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(Stack) userInfo:nil repeats:YES];
// self.modalPresentationCapturesStatusBarAppearance = YES;
}
#pragma mark - 横屏代码
- (BOOL)shouldAutorotate{
return NO;
} //NS_AVAILABLE_IOS(6_0);当前viewcontroller是否支持转屏
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscape;
} //当前viewcontroller支持哪些转屏方向
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeRight;
}
- (BOOL)prefersStatusBarHidden
{
return NO; // 返回NO表示要显示,返回YES将hiden
}
#pragma mark - 创建UISlider
- (void)createSlider
{
self.slider = [[UISlider alloc]initWithFrame:CGRectMake(100, 345, _width * 0.7, 15)];
[self.backView addSubview:_slider];
[_slider setThumbImage:[UIImage imageNamed:@"iconfont-yuan.png"] forState:UIControlStateNormal];
[_slider addTarget:self action:@selector(progressSlider:) forControlEvents:UIControlEventValueChanged];
_slider.minimumTrackTintColor = [UIColor colorWithRed:30 / 255.0 green:80 / 255.0 blue:100 / 255.0 alpha:1];
}
#pragma mark - slider滑动事件
- (void)progressSlider:(UISlider *)slider
{
//拖动改变视频播放进度
if (_player.status == AVPlayerStatusReadyToPlay) {
// //计算出拖动的当前秒数
CGFloat total = (CGFloat)_playerItem.duration.value / _playerItem.duration.timescale;
// NSLog(@"%f", total);
NSInteger dragedSeconds = floorf(total * slider.value);
// NSLog(@"dragedSeconds:%ld",dragedSeconds);
//转换成CMTime才能给player来控制播放进度
CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1);
[_player pause];
[_player seekToTime:dragedCMTime completionHandler:^(BOOL finish){
[_player play];
}];
}
}
#pragma mark - 创建UIProgressView
- (void)createProgress
{
self.progress = [[UIProgressView alloc]initWithFrame:CGRectMake(102, 352, _width * 0.69, 15)];
[_backView addSubview:_progress];
}
#pragma mark -
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryNSString *,id *)change context:(void *)context
{
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSTimeInterval timeInterval = [self availableDuration];// 计算缓冲进度
// NSLog(@"Time Interval:%f",timeInterval);
CMTime duration = self.playerItem.duration;
CGFloat totalDuration = CMTimeGetSeconds(duration);
[self.progress setProgress:timeInterval / totalDuration animated:NO];
}
}
我觉得
进度条自己做的,根据影片信息中带的分隔位置标示进行显示
如何将视频添加上自定义的渲染效果,并显示?
1、解码视频
2、获取视频帧
3、渲染视频帧
4、显示渲染后的视频帧
5、编码视频帧,生成新的视频
AVPlayer:驱动播放用例的中心阶层,是用于管理媒体资产的回放和定时的控制器对象
这里AVPlayer,我制作简单的播放,暂停,seek。并且添加上AVPlayerItemVideoOutput做一个视频帧输出的工作。
创建一个播放器
AVPlayerItemVideoOutput 获取视频帧
主要的核心工具是 AVPlayerItemVideoOutput ,这对象相当于一个视频解码工具,对它进行属性设置,可以获取视频中某一时刻的想要数据的 CVPixelBuffer 视频帧。
通过获取到的CVPixelBuffer,进行OPenGL自定义渲染显示。
外部需要开启一个定时器,来实时的进行画面的刷新。定时器时间可以根据视频的FPS来控制。
至此如何获取视频帧就可以了。
如何获取视频帧,这里都比较简单,都是通过系统层去实现功能。
主要注意的是:
1、AVPlayerItemVideoOutput的获取的数据格式定义,根据 需求设置RGBA还是YUV420的数据 。
2、AVPlayer使用seek时候,使用 精度比较高的方法 ,提高在seek时候的画面流畅度
3、获取的CVPixelBuffer 在Swift语言,不需要手动释放 。在OC上需要调用 CVPixelBufferRelease() 手动释放
Git Code:AVPlayer-Render
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流