参考:https://blog.csdn.net/leixiaohua1020/article/details/15811977/
《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频:https://blog.csdn.net/leixiaohua1020/article/details/47068015
参考:iOS中集成ijkplayer视频直播框架:https://www.jianshu.com/p/1f06b27b3ac0
参考直播demo:https://www.jianshu.com/p/b8db6c142aad
参考:https://www.jianshu.com/u/b09c3959ab3b
参考:http://www.cnblogs.com/fusheng-it/p/7911000.html(直播)
参考:https://www.jianshu.com/p/bd42bacbe4cc(直播原理)
视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染
直播前期准备为:
1.推流用优酷开源的LFLiveKit框架。
2.拉流(实际上就是一个播放器)用ijkplayer 框架,当然这个也是开源的。
3.创建本地rtmp服务器。
推流:
推流用的是一个第三方的IFliveKit框架。这个框架基于rtmp协议。IFLiveKit内部集成了GPUIImage。内部实现了图片渲染等美艳效果。减少了开发时候美艳效果的调试。
推流端工作将它细分为以下几个部分(基本上是依次执行的):
一:相机相册权限检查并作出相应的处理方法。
二:音频视频信息配置(码率,采样率,质量等信息)
三:音频视频采集及编码前的滤镜等效果(GPUIImage)
四:音频视频编码。这里需要注意的是iOS8以上支持硬件编码,如果不能适配iOS8以上是需要做处理的(参考LFLiveKit)
五:上传数据(rtmp)
一个简单的推流页面应该包含以下几个功能:
1.推流状态监听。2.切换摄像头。3.切换美艳效果。4.开关推流。
一.推流之前需要检查摄像头和麦克风等权限是否开启,并启动摄像头,核心代码如下:
//判断是否有摄像头if(![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
[self showInfo:@"您的设备没有摄像头或者相关的驱动, 不能进行直播"];
return;
}
//判断是否有摄像头权限AVAuthorizationStatus authorizationStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authorizationStatus == AVAuthorizationStatusRestricted|| authorizationStatus == AVAuthorizationStatusDenied) {
[self showInfo:@"app需要访问您的摄像头。\n请启用摄像头-设置/隐私/摄像头"];
return ;
}AVAudioSession *audioSession = [AVAudioSession sharedInstance];
if ([audioSession respondsToSelector:@selector(requestRecordPermission:)]) {
[audioSession performSelector:@selector(requestRecordPermission:) withObject:^(BOOL granted) {
if (granted) {
return YES;
}
else {
[self showInfo:@"app需要访问您的麦克风。\n请启用麦克风-设置/隐私/麦克风"];
return NO;
}
}];
}
//检查麦克风权限- (void)checkCaptureAudioDeviceEnableCheckCaptureVideo:(void(^)(BOOL isAutioSucc,NSString * err))succ{
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]==AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
if (granted) {succ(granted,nil);}
else{succ(NO,@"app需要访问您的麦克风。\n请启用麦克风-设置/隐私/麦克风");}
}];
}elseif([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]==AVAuthorizationStatusAuthorized){
succ(YES,nil);
}else{
succ(NO,@"app需要访问您的麦克风。\n请启用麦克风-设置/隐私/麦克风");
}
}
二.创建一个按钮.点击开始推流代码如下:
- (LFLiveSession*)session{
if(!_session){
/*** 默认分辨率368 * 640 音频:44.1 iphone6以上48 双声道 方向竖屏 ***/ _session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium2]];
/** 自己定制高质量音频128K 分辨率设置为720*1280 方向竖屏 *//* LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
audioConfiguration.numberOfChannels = 2;
audioConfiguration.audioBitrate = LFLiveAudioBitRate_128Kbps;
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
videoConfiguration.videoSize = CGSizeMake(720, 1280);
videoConfiguration.videoBitRate = 800*1024;
videoConfiguration.videoMaxBitRate = 1000*1024;
videoConfiguration.videoMinBitRate = 500*1024;
videoConfiguration.videoFrameRate = 15;
videoConfiguration.videoMaxKeyframeInterval = 30;
videoConfiguration.orientation = UIInterfaceOrientationPortrait;
videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration liveType:LFLiveRTMP];
*/// 设置代理_session.delegate= self;
_session.running = YES;
_session.preView = self.livingPreView;
}
return _session;
}
//给服务器推流
- (IBAction)startTouched:(id)sender {
LFLiveStreamInfo *stream = [LFLiveStreamInfonew];
// 本地推流地址stream.url =@"rtmp://192.168.199.131:1935/rtmplive/room";
self.rtmpUrl = stream.url;
[self.session startLive:stream];
}
3.创建一个按钮点击关闭推流,代码如下:
- (IBAction)endTouched:(id)sender {
// 结束直播 [self.session stopLive];
self.stateLable.text = [NSString stringWithFormat:@"状态: 直播被关闭\nRTMP: %@", self.rtmpUrl];
}
4.创建一个按钮点击切换前后摄像头,代码如下:
- (IBAction)camaBtnTouched:(id)sender {
AVCaptureDevicePosition devicePositon = self.session.captureDevicePosition;
self.session.captureDevicePosition = (devicePositon == AVCaptureDevicePositionBack) ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
NSLog(@"切换前置/后置摄像头");
}
5.创建一个按钮设置美艳功能,代码如下:
- (IBAction)beautiyBtnTouched:(id)sender {
((UIButton*)sender).selected = !((UIButton*)sender).selected;
// 默认是开启了美颜功能的self.session.beautyFace = !self.session.beautyFace;
}
6.推流状态监听,接受代理,代码如下:
#pragmamark -- LFStreamingSessionDelegate/** live status changed will callback */- (void)liveSession:(nullable LFLiveSession *)session liveStateDidChange:(LFLiveState)state{
NSString *tempStatus;
switch (state) {
case LFLiveReady:
tempStatus =@"准备中";
break;
case LFLivePending:
tempStatus =@"连接中";
break;
case LFLiveStart:
tempStatus =@"已连接";
break;
case LFLiveStop:
tempStatus =@"已断开";
break;
case LFLiveError:
tempStatus =@"连接出错";
break;
default:
break;
}
self.stateLable.text = [NSString stringWithFormat:@"状态: %@\nRTMP: %@", tempStatus, self.rtmpUrl];
}/** live debug info callback */- (void)liveSession:(nullable LFLiveSession *)session debugInfo:(nullable LFLiveDebug*)debugInfo{
}/** callback socket errorcode */- (void)liveSession:(nullable LFLiveSession*)session errorCode:(LFLiveSocketErrorCode)errorCode{
}
6.自己也需要看到自己的推流画面,并观察美艳效果,代码如下:
- (UIView *)livingPreView
{
if(!_livingPreView) {
UIView *livingPreView = [[UIView alloc] initWithFrame:self.view.bounds];
livingPreView.backgroundColor = [UIColor clearColor];
livingPreView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:livingPreView atIndex:0];
_livingPreView = livingPreView;
}
return _livingPreView;
}
至此,推流完成。
拉流:
推流完成后,需要拉流才能进行完整的直播。拉流我们采用的也是一个开源的第三方库IJKMediaFramework。
这个库本质是一个播放器,能播放flv格式的播放器。用起来和ios自带的AVPlayer很相似。
实现功能:1.拉流播放。2.监听。
一.创建占位图和卡顿占位动效,代码如下:
//直播前的占位图片
- (UIImageView *)placeHolderView
{
if(!_placeHolderView) {
_placeHolderView = [[UIImageView alloc] init];
_placeHolderView.frame = self.view.bounds;
_placeHolderView.image = [UIImage imageNamed:@"profile_user_414x414"];
// 强制布局 [_placeHolderView layoutIfNeeded];
}
return_placeHolderView;}
//卡顿占位动效
- (void)showActivityView{
if(!_activity) {
_activity= [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
_activity.frame = CGRectMake((SCREAM_WEIGHT-100)*0.5, (SCREAM_HIGHT-100)*0.5,100,100);
}
[self.activity startAnimating];
[self.view addSubview:self.activity];
}
//关闭卡顿占位动效- (void)stopActivityView{
if ([_activity isAnimating]) {
[_activity startAnimating];
}
[_activity removeFromSuperview];
_activity = nil;
}
二.拉流播放(创建播放器播放),代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.placeHolderView];
[self showActivityView];
IJKFFOptions *options = [IJKFFOptions optionsByDefault];
[options setPlayerOptionIntValue:1forKey:@"videotoolbox"];
// 帧速率(fps) (可以改,确认非标准桢率会导致音画不同步,所以只能设定为15或者29.97)[options setPlayerOptionIntValue:29.97forKey:@"r"];
// -vol——设置音量大小,256为标准音量。(要设置成两倍音量时则输入512,依此类推[options setPlayerOptionIntValue:512forKey:@"vol"];
IJKFFMoviePlayerController *moviePlayer = [[IJKFFMoviePlayerController alloc] initWithContentURLString:PLAY_URL withOptions:options];
moviePlayer.view.frame = self.view.bounds;
moviePlayer.scalingMode = IJKMPMovieScalingModeAspectFill;
// 设置自动播放(必须设置为NO, 防止自动播放, 才能更好的控制直播的状态)moviePlayer.shouldAutoplay = NO;
// 默认不显示moviePlayer.shouldShowHudView = NO;
[self.view insertSubview:moviePlayer.view atIndex:0];
[moviePlayer prepareToPlay];
self.moviePlayer = moviePlayer;
// 设置监听 [self addObserver];
[self.view addSubview:self.outBtn];
}
三.设置监听(主要是监听缓存情况),代码如下:
- (void)addObserver
{
//监听加载状态改变通知[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadStateDidChange:) name:IJKMPMoviePlayerLoadStateDidChangeNotificationobject:self.moviePlayer];
}- (void)loadStateDidChange:(NSNotification *) notification
{
//状态为缓冲几乎完成,可以连续播放if((self.moviePlayer.loadState & IJKMPMovieLoadStatePlaythroughOK) !=0) {
if(!self.moviePlayer.isPlaying) {
//开始播放 [self.moviePlayer play];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (_placeHolderView) {
[_placeHolderView removeFromSuperview];
_placeHolderView = nil;
}
[self stopActivityView];
});
}else{
// 如果是网络状态不好, 断开后恢复, 也需要去掉加载if ([_activity isAnimating]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self stopActivityView];
});
}
}
}
//缓冲中elseif(self.moviePlayer.loadState & IJKMPMovieLoadStateStalled){
[self showActivityView];
/*
这里主播可能已经结束直播了。我们需要请求服务器查看主播是否已经结束直播。
方法:
1、从服务器获取主播是否已经关闭直播。
优点:能够正确的获取主播端是否正在直播。
缺点:主播端异常crash的情况下是没有办法通知服务器该直播关闭的。
2、用户http请求该地址,若请求成功表示直播未结束,否则结束
优点:能够真实的获取主播端是否有推流数据
缺点:如果主播端丢包率太低,但是能够恢复的情况下,数据请求同样是失败的。
*/ }
}
四.记得关闭前释放:
- (void)dealloc{
if (_moviePlayer) {
[_moviePlayer shutdown];
[_moviePlayer.view removeFromSuperview];
_moviePlayer = nil;
}
[[NSNotificationCenter defaultCenter]removeObserver:self];
}