既然推拉流,就需要先搭建一个推拉流服务器,nginx服务器的轻量多并发特性,使其很适合作为推拉流服务器,我们就搭建一个nginx服务器,用做推拉流服务器
一、安装Homebrow
已经安装了brow的可以直接跳过这一步。
执行命令
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
如果已经安装过,而想要卸载:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
二、安装nginx
先glone nginx项目到本地:
brew tap homebrew/nginx
如果报错的话,可以重复1步骤,并将1的地址换为:
https://github.com/denji/homebrew-nginx
然后:brew tap denji/nginx
执行安装:
brew install nginx-full --with-rtmp-module
安装过程比较缓慢,耐心等待
通过操作以上步骤nginx和rtmp模块就安装好了,下面开始来配置nginx的rtmp模块
首先来看看我们的nginx安装在哪里了
brew info nginx-full
执行上面的命令后我们可以看到信息
nginx安装所在位置
/usr/local/Cellar/nginx-full/1.10.1/bin/nginx
nginx配置文件所在位置
/usr/local/etc/nginx/nginx.conf
三、运行nginx
执行命令 ,测试下是否能成功启动nginx服务
nginx
命令行如下图所示
在浏览器地址栏输入:http://localhost:8080 (直接点击)
如果出现
代表nginx安装成功了
如果终端上提示
nginx: [emerg] bind() to 0.0.0.0:8080 failed (48: Address already in use)
则表示8080
端口被占用了, 查看端口PID
lsof -i tcp:8080
kill掉占用8080端口的PID
kill 9603(这里替换成占用8080端口的PID)
然后重新执行nginx
nginx常用方法:重新加载配置文件
nginx -s reload
重新加载日志:
nginx -s reopen
// 停止 nginx
nginx -s stop
// 有序退出 nginx
nginx -s quit
四、配置rtmp
现在我们来修改nginx.conf这个配置文件,配置rtmp
复制nginx配置文件所在位置
/usr/local/etc/nginx/nginx.conf
打开Finder Shift + command + G前往,用记事本工具打开nginx.conf
http {
……
}
在http节点后面加上rtmp配置:
rtmp {
server {
listen 1935;
#直播流配置
application rtmplive {
live on;
#为 rtmp 引擎设置最大连接数。默认为 off
max_connections 1024;
}
application hls{
live on;
hls on;
hls_path /usr/local/var/www/hls;
hls_fragment 1s;
}
}
}
六、安装ffmepg工具
brew install ffmpeg
安装这个需要等一段时间等待吧 然后准备一个视频文件作为来推流,然后我们在安装一个支持rtmp协议的视频播放器,Mac下可以用VLC
显示下图表示ffmpeg安装完毕
ffmepg 安装完成后可以开始推流了
现在开始创建xcode文件吧~推流端用的是LFLiveKit框架,拉流用IJKPlayer,先看下整个文件目录
可以使用cocoapod导入(至于怎么使用cocoapods,我的其他文章里有介绍和转载,
)sudo gem install -n /usr/local/bin cocoapods /
sudo chmod +rx /usr/local/bin
是的,没有看错,几个文件就能完成整个推拉流的过程╮(╯╰)╭ 主要实现是HBVideoChatViewController文件
//
// HBVideoChatViewController.m
// 视频聊天
@interface HBVideoChatViewController ()<LFLiveSessionDelegate>
//当前区域网所在IP地址
@property (nonatomic,copy) NSString *ipAddress;
//我的房间号
@property (nonatomic,copy) NSString *myRoom;
//别人的房间号
@property (nonatomic,copy) NSString *othersRoom;
//ip后缀(如果用本地服务器,则为在nginx.conf文件中写的rtmplive)
@property (nonatomic, copy) NSString *suffix;
//大视图
@property (nonatomic,weak) UIView *bigView;
//小视图
@property (nonatomic,weak) UIView *smallView;
//播放器
@property (nonatomic,strong) IJKFFMoviePlayerController *player;
//推流会话
@property (nonatomic,strong) LFLiveSession *session;
@end
推流
LFLiveKit这个推流框架的关键类是LFLiveSession,也是依靠着个类来实现推流的,底层的实现则是对ffmpeg的封装,有兴趣的童鞋可以去研究研究,废话少说上代码~
首先,创建session并进行一些配置
- (LFLiveSession *)session{
if (_session == nil) {
//初始化session要传入音频配置和视频配置
//音频的默认配置为:采样率44.1 双声道
//视频默认分辨率为360 * 640
_session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Low1] ];
//设置返回的视频显示在指定的view上
_session.preView = self.smallView;
_session.delegate = self;
//是否输出调试信息
_session.showDebugInfo = NO;
}
return _session;
}
LFLiveAudioConfiguration和LFLiveVideoConfiguration都可以进行定制,配置的高低影响传输的速度和质量,具体的文档都有中文注释写得很清楚
接着向系统请求设备授权
/**
* 请求摄像头资源
*/
- (void)requesetAccessForVideo{
__weak typeof(self) weakSelf = self;
//判断授权状态
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusNotDetermined:{
//发起授权请求
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
//运行会话
[weakSelf.session setRunning:YES];
});
}
}];
break;
}
case AVAuthorizationStatusAuthorized:{
//已授权则继续
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.session setRunning:YES];
});
break;
}
default:
break;
}
}
/**
* 请求音频资源
*/
- (void)requesetAccessForMedio{
__weak typeof(self) weakSelf = self;
//判断授权状态
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusNotDetermined:{
//发起授权请求
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
//运行会话
[weakSelf.session setRunning:YES];
});
}
}];
break;
}
case AVAuthorizationStatusAuthorized:{
//已授权则继续
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.session setRunning:YES];
});
break;
}
default:
break;
}
}
通过代理方法来处理连接异常
//连接错误回调
- (void)liveSession:(nullable LFLiveSession *)session errorCode:(LFLiveSocketErrorCode)errorCode{
//弹出警告
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Warning" message:@"连接错误,请检查IP地址后重试" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"sure" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self.navigationController popViewControllerAnimated:YES];
}];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
全部设置好就可以开始推流啦~
- (void)viewDidLoad{
...
// 推流端
[self requesetAccessForVideo];
[self requesetAccessForMedio];
[self startLive];
...
}
- (void)startLive{
//RTMP要设置推流地址
LFLiveStreamInfo *streamInfo = [LFLiveStreamInfo new];
streamInfo.url = [NSString stringWithFormat:@"rtmp://%@:1935/%@/%@",self.ipAddress,self.suffix,self.myRoom];
[self.session startLive:streamInfo];
}
- (void)stopLive{
[self.session stopLive];
}
拉流
用IJKPlayer进行拉流,具体的编译和集成步骤可以看iOS中集成ijkplayer视频直播框架,也可以直接将我编译好的IJK拖到项目中即可,在文章最后会给出下载地址
对播放器进行初始化
-(IJKFFMoviePlayerController *)player{
if (_player == nil) {
IJKFFOptions *options = [IJKFFOptions optionsByDefault];
_player = [[IJKFFMoviePlayerController alloc] initWithContentURLString:[NSString stringWithFormat:@"rtmp://%@:1935/%@/%@",self.ipAddress,self.suffix,self.othersRoom] withOptions:options];
//设置填充模式
_player.scalingMode = IJKMPMovieScalingModeAspectFill;
//设置播放视图
_player.view.frame = self.bigView.bounds;
[self.bigView addSubview:_player.view];
//设置自动播放
_player.shouldAutoplay = YES;
[_player prepareToPlay];
}
return _player;
}
设置播放器播放通知监听
- (void)initPlayerObserver{
//监听网络状态改变
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadStateDidChange:) name:IJKMPMoviePlayerLoadStateDidChangeNotification object:self.player];
//监听播放网络状态改变
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playStateDidChange:) name:IJKMPMoviePlayerPlaybackStateDidChangeNotification object:self.player];
}
//网络状态改变通知响应
- (void)loadStateDidChange:(NSNotification *)notification{
IJKMPMovieLoadState loadState = self.player.loadState;
if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
NSLog(@"LoadStateDidChange: 可以开始播放的状态: %d\\n",(int)loadState);
}else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\\n", (int)loadState);
} else {
NSLog(@"loadStateDidChange: ???: %d\\n", (int)loadState);
}
}
//播放状态改变通知响应
- (void)playStateDidChange:(NSNotification *)notification{
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;
}
}
}
接着在viewDidLoad中调用方法并开始播放
- (void)viewDidLoad {
[super viewDidLoad];
...
// 播放端
[self initPlayerObserver];
[self.player play];
}
大功告成,现在只要传入正确的参数就能实现视频聊天啦╰( ̄ ̄)╮
//
// HBVideoChatViewController.h
// 视频聊天
//
// Created by apple on 16/8/9.
// Copyright © 2016年 yhb. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface HBVideoChatViewController : UIViewController
/**
* 创建视频聊天播放器
*
* @param IPAddress 两个人共同所在的区域网
* @param myRoom 我的推流后缀地址(随便写,只要与别人的othersRoom相同即可)
* @param othersRoom 别人的推流地址
*
*/
- (instancetype)initWithIPAddress:(NSString *)ipAddress MyRoom:(NSString *)myRoom othersRoom:(NSString *)othersRoom;
@end
辅助文件
在MainViewController中导入刚写的文件并设置一个alertView来传入参数
//
// TestViewController.m
// VideoChat
//
// Created by apple on 16/8/10.
// Copyright © 2016年 yhb. All rights reserved.
//
#import "MainViewController.h"
#import "HBVideoChatViewController.h"
@interface MainViewController ()
@property (nonatomic,copy) NSString *ipAddress;
@property (nonatomic,copy) NSString *myRoom;
@property (nonatomic, copy) NSString *othersRoom;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self setButton];
}
- (void)setButton{
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:@"跳转到视频聊天界面" forState:UIControlStateNormal];
button.frame = CGRectMake(0, 0, 200, 50);
button.center = self.view.center;
[button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonClick{
//弹出输入框
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Info" message:@"请输入详细信息" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"请输入区域网IP";
}];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"请输入你的房间号";
}];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"请输入对方的房间号";
}];
//点击确定按钮跳转界面
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// @"192.168.15.32"
//取到文本数据传值
HBVideoChatViewController *viewController = [[HBVideoChatViewController alloc] initWithIPAddress:[alert.textFields[0] text] MyRoom:[alert.textFields[1] text] othersRoom:[alert.textFields[2] text]];
[self.navigationController pushViewController:viewController animated:YES];
}];
//取消按钮
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
@end
现在用终端推下桌面测试下,模拟器没摄像头所以就没画面了╮(╯_╰)╭
在终端输入ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f flv rtmp://localhost:1935/rtmplive/home
将自己的桌面推送到服务器上,然后运行模拟器,输入对应IP地址,效果如下
rtmp://localhost:1935/rtmplive/home是我本地的服务器,对应的是自己的IP,我的是192.168.15.30,见下图
注意:用真机测试时,要确保手机wifi连接到所搭建服务器的区域网
推拉流设置的rtmp地址的IP前缀,是你搭建的nginx服务器的IP地址,而且要保证nginx服务器和你推拉流的设备,在统一Wi-Fi环境下.(当然如果有条件的话,可以设置nginx为公网IP,推拉流设备直接开4g网络,将推流到的IP设置为nginx的公网IP).
测试了下大概有3 5秒的延迟,现在在同一区域网下输入对方的房间号就可以实现视频聊天啦
完整项目:Github
网络服务器(不一定可用):rtmp://60.174.36.89:1935/live/xxx
打包好的IJKPlayer:https://pan.baidu.com/s/1o7Frs06
下载解压后直接拖进项目即可
一些关于直播原理和延迟卡顿优化的文章:
http://blog.csdn.net/zhonggaorong/article/details/51483282
http://toutiao.com/i6278412629417394689/
http://blog.ucloud.cn/archives/694