这里先说一下推流的实现,关于拉流的实现https://www.jianshu.com/p/7e3dd597cfa5
demo下载地址:https://pan.baidu.com/s/1fU5SMyEDRAVaGBIBvYzbVw
在说直播推流技术之前,先说一下一些关于直播技术的背景知识。2016年是直播元年,直播技术如此火热,作为一个开发人员应该去学习一下。先说下直播推流的过程:采集——>前处理——>编码-—>推流———>流分发———>播放。
1.采集:音视频采集 pc段屏幕摄像头采集 iOS和安卓端的摄像头和屏幕采集
2.前处理:主要包括美颜,模糊效果,水印。iOS端一般会用到GPUImage处理图像,安卓端一般使用Google的grafika(图形处理库)
3.编码:不经过编码的视频体积会比较庞大。音视频必须经过压缩编辑才能进行存储和传输。
编码方式:硬编码(通过非CPU,如显卡GPU)和软编码(使用CPU),最好使用硬编码。
编码标准:视频:H.265 H.264 VP8 VP9 音频:AAC Opus
4.传输:从推流端到服务器。 常见的传输协议:RTMP RTSP HLS
5.流分发:在服务器端做一些处理。比如鉴黄 转码成不同的格式支持不同协议,以适应各个平台
6.播放 (解协议—解封装—音视频解码—音视频同步—音视频播放)
解协议:取出网络传输过程中一些无用的信息
解封装:获取到的是音频和视频在一起的封装文件
==========================================================
接下来步入正题,这里推流技术的实现我们主要基于bilibili的ijkPlayer第三方开源框架,这个开源框架已经帮我们集成好了FFmpeg。苹果的播放器也是基于FFMpeg实现的,但是不能播放直播类视频。这个第三方开源框架十分强大,安卓移动端的直播很多也是基于这个框架。斗鱼的直播就是基于ijkPlayer实现的,所以说这个开源框架还是很靠谱的,斗鱼这么火的一个直播平都是基于它实现的,我们没有理由不相信它。
==========================================================
先来看一下ijkPlayer的运行效果,在这之前我们要做一些准备操作,才能看到官方提供的demo的运行效果。
1.Github上搜索ijkPlayer,并下载下来。
2.打开终端,输入命令: cd 第一步下载的ijkplayer这个文件名
- ./init-ios.sh (下载ffmpeg的过程,可能很漫长)
- 第三步骤执行完成后, cd ios
- ./compile-ffmpeg.sh clean (编译过程,可能比较耗时间)
- ./compile-ffmpeg.sh all (编译过程,可能比较耗时间)
执行以上步骤后,就可以运行ijkplayer —> ios —>IJKMediaDemo这个Demo了,但是bilibili提供的直播地址貌似不是很好用,可以自己找一个合适的直播地址进行测试。
==========================================================
已经运行了demo,看了项目,接下来看看如何集成到我们自己的项目中。主要有两种方法:1.工程中集成工程 。2.把ijkplayer源码打包成framework,然后将这个framework集成到我们的项目中。第一种方法相对而言比较麻烦,所以这里我们采用生成framework的方式。可以参考这篇博客:http://www.jianshu.com/p/c1ea1c249701 如何制作.a 和 .framework静态库,这里我就简单说一下。生成framework的步骤如下:
1.打开ijkplayer —> ios 目录下的IJKMediaPlayer这个工程。
2.调成release版本。(默认是debug版本),操作步骤见下图。
3.然后分别选择模拟器和Generic iOS Device,分别编译一下。
4.合并模拟器和Generic iOS Device编译生成的framework文件。注意:这里合并的内容并不是Bundle文件,而是Bundle文件下的IJKMediaFramework。如果是上线的话,我们可以不合并framework文件,而是直接使用Generic iOS Device生成的framework,这样可以减小项目资源文件大小。之所以执行合并这个步骤,是因为在实际开发中,不仅仅要在真机上测试,还要在模拟器上运行。
5.将上面生成的IJKMediaFramework.framework包文件,直接拉到工程的文件中,然后导入如下依赖框架:
AudioToolbox.framework、AVFoundation.framework、CoreGraphics.framework、CoreMedia.framework、CoreVideo.framework、libbz2.tbd、libz.tbd、MediaPlayer.framework、MobileCoreServices.framework、OpenGLES.framework、QuartzCore.framework、UIKit.framework、VideoToolbox.framework。此时编译一下,应该接没有问题了。
==========================================================
接下来就可以开始代码实现部分了,注释会在代码中说明。以下代码是我之前写过的代码,所以在这里就说明一些事项。创建一个PlayerViewController类,在.h文件中添加一个属性@property (nonatomic, strong) Live * live;其中Live是上一界面传入的一个模型对象,live.streamAddr属性是播放的地址,live.creator.portrait是毛玻璃特效显示模糊处理的图片地址。另外以下代码中有些代码会和界面布局有关系,有刚进入播放界面的毛玻璃特效。另外,还添加了一个子控制器,并将子控制器的view显示到PlayerViewController控制器的view上。这个控制器主要是起到控制面板的作用,类似直播界面中的一些发送小礼物,聊天等控能,都在这个控制器面板中实现。
#import "PlayerViewController.h"
//导入头文件
#import <IJKMediaFramework/IJKMediaFramework.h>
#import "AppDelegate.h"
#import "SXTLiveChatViewController.h"
@interface PlayerViewController ()
//注意这里是atomic**************************
@property(atomic, retain) id<IJKMediaPlayback> player;
@property(nonatomic,strong)UIImageView *blurImageView;
@property (nonatomic, strong) UIButton * closeBtn;
//添加直播控制面板
@property (nonatomic, strong) SXTLiveChatViewController * liveChatVC;
@end
@implementation SXTPlayerViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initPlayer];
[self initUI];
[self addChildVC];
}
- (void)initUI{
//这和毛玻璃效果有关系
self.view.backgroundColor = [UIColor blackColor];
self.blurImageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
[self.blurImageView downloadImage:[NSString stringWithFormat:@"%@%@",IMAGE_HOST,self.live.creator.portrait] placeholder:@"default_room"];
[self.view addSubview:self.blurImageView];
// 创建需要的毛玻璃特效类型
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
// 创建毛玻璃view 视图
UIVisualEffectView *effectView = [[UIVisualEffectView alloc]initWithEffect:blurEffect];
effectView.frame = self.blurImageView.bounds;
//添加到要有毛玻璃特效的控件中
[self.blurImageView addSubview:effectView];
//[self.view addSubview:self.closeBtn];
}
- (void)initPlayer {
IJKFFOptions *options = [IJKFFOptions optionsByDefault];
self.player = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:self.live.streamAddr] withOptions:options];
self.player.view.frame = self.view.bounds;
//设置自动播放
self.player.shouldAutoplay = YES;
/******************************************/
//因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上
//添加player的view到self.view上
[self.view addSubview:self.player.view];
}
//添加控制面板
- (void)addChildVC{
//添加子控制器
[self addChildViewController:self.liveChatVC];
//添加子控制器视图
[self.view addSubview:self.liveChatVC.view];
[self.liveChatVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
self.liveChatVC.live = self.live;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.navigationBarHidden = YES;
//注册直播需要的通知
[self installMovieNotificationObservers];
//准备播放
[self.player prepareToPlay];
/******************************************/
//因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上。但是要注意:视图离开时要移除退出按钮
UIWindow * window = [(AppDelegate *)[UIApplication sharedApplication].delegate window];
[window addSubview:self.closeBtn];
}
- (UIButton *)closeBtn {
if (!_closeBtn) {
UIImage * image = [UIImage imageNamed:@"mg_room_btn_guan_h"];
_closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeBtn setImage:image forState:UIControlStateNormal];
_closeBtn.frame = CGRectMake(SCREEN_WIDTH - image.size.width - 10, SCREEN_HEIGHT - image.size.height - 10, image.size.width, image.size.height);
[_closeBtn addTarget:self action:@selector(closeLive:) forControlEvents:UIControlEventTouchUpInside];
}
return _closeBtn;
}
- (void)closeLive:(UIButton *)button {
[self.navigationController popViewControllerAnimated:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBarHidden = NO;
//关闭直播
[self.player shutdown];
//移除直播通知
[self removeMovieNotificationObservers];
/******************************************/
//因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上。但是要注意:视图离开时要移除退出按钮
[self.closeBtn removeFromSuperview];
}
#pragma mark -通知要实现的四个方法
- (void)loadStateDidChange:(NSNotification*)notification
{
// MPMovieLoadStateUnknown = 0, 未知
// MPMovieLoadStatePlayable = 1 << 0, 缓冲结束可以播放
// MPMovieLoadStatePlaythroughOK = 1 << 1, // Playback will be automatically started in this state when shouldAutoplay is YES 缓冲结束自动播放
// MPMovieLoadStateStalled = 1 << 2, // Playback will be automatically paused in this state, if started 暂停
IJKMPMovieLoadState loadState = _player.loadState;
if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
NSLog(@"loadStateDidChange: IJKMPMovieLoadStatePlaythroughOK: %d\n", (int)loadState);
} else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\n", (int)loadState);
} else {
NSLog(@"loadStateDidChange: ???: %d\n", (int)loadState);
}
}
- (void)moviePlayBackDidFinish:(NSNotification*)notification
{
// MPMovieFinishReasonPlaybackEnded, 直播结束
// MPMovieFinishReasonPlaybackError, 直播错误
// MPMovieFinishReasonUserExited 用户退出
int reason = [[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
switch (reason)
{
case IJKMPMovieFinishReasonPlaybackEnded:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackEnded: %d\n", reason);
break;
case IJKMPMovieFinishReasonUserExited:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonUserExited: %d\n", reason);
break;
case IJKMPMovieFinishReasonPlaybackError:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackError: %d\n", reason);
break;
default:
NSLog(@"playbackPlayBackDidFinish: ???: %d\n", reason);
break;
}
}
- (void)mediaIsPreparedToPlayDidChange:(NSNotification*)notification
{
NSLog(@"mediaIsPreparedToPlayDidChange\n");
}
- (void)moviePlayBackStateDidChange:(NSNotification*)notification
{
// MPMoviePlaybackStateStopped, 停止
// MPMoviePlaybackStatePlaying, 播放
// MPMoviePlaybackStatePaused, 暂停
// MPMoviePlaybackStateInterrupted,
// MPMoviePlaybackStateSeekingForward, 前进
// MPMoviePlaybackStateSeekingBackward 后退
switch (_player.playbackState)
{
case IJKMPMoviePlaybackStateStopped: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: stoped", (int)_player.playbackState);
break;
}
case IJKMPMoviePlaybackStatePlaying: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: playing", (int)_player.playbackState);
break;
}
case IJKMPMoviePlaybackStatePaused: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: paused", (int)_player.playbackState);
break;
}
case IJKMPMoviePlaybackStateInterrupted: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: interrupted", (int)_player.playbackState);
break;
}
case IJKMPMoviePlaybackStateSeekingForward:
case IJKMPMoviePlaybackStateSeekingBackward: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: seeking", (int)_player.playbackState);
break;
}
default: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: unknown", (int)_player.playbackState);
break;
}
}
/*****************************************/
//只要有变化就移除掉毛玻璃效果
//开始播放直播的时候要移除毛玻璃效果
self.blurImageView.hidden = YES;
[self.blurImageView removeFromSuperview];
}
#pragma mark Install Movie Notifications
/* Register observers for the various movie object notifications. */
-(void)installMovieNotificationObservers
{
//监听网络环境,监听缓冲方法
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loadStateDidChange:)
name:IJKMPMoviePlayerLoadStateDidChangeNotification
object:_player];
//监听直播完成回调
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayBackDidFinish:)
name:IJKMPMoviePlayerPlaybackDidFinishNotification
object:_player];
//
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mediaIsPreparedToPlayDidChange:)
name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
object:_player];
//监听用户的主动操作
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayBackStateDidChange:)
name:IJKMPMoviePlayerPlaybackStateDidChangeNotification
object:_player];
}
#pragma mark Remove Movie Notification Handlers
/* Remove the movie notification observers from the movie object. */
-(void)removeMovieNotificationObservers
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerLoadStateDidChangeNotification object:_player];
[[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackDidFinishNotification object:_player];
[[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification object:_player];
[[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackStateDidChangeNotification object:_player];
}
- (SXTLiveChatViewController *)liveChatVC {
if (!_liveChatVC) {
_liveChatVC = [[SXTLiveChatViewController alloc] init];
}
return _liveChatVC;
}
@end
网友评论