一步一步带你实现 iOS 直播 Demo

推流:LFLiveKit 播放:ijkplayer 服务器:nginx+rtmp

首先开发一款直播 APP 首先是播放, 前提是服务器只要提供拉流的 url, 下面就主要是通过ijkplayer, 来实现播放; 直播源, 也就是 url 是从映客抓的, 直接上代码

- (void)loadData{

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/plain", @"text/html", nil];
[manager GET:@"http://116.211.167.106/api/live/aggregation?uid=23455&interest=1" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    
    if ([responseObject[@"error_msg"] isEqualToString:@"操作成功"]) {
        self.dataArray = [LiveModel mj_objectArrayWithKeyValuesArray:responseObject[@"lives"]];
        [self.tableView reloadData];
    }
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    
    NSLog(@"%@",error);
}];
}

有了数据源, 实现 tableView 代理方法即可, 如下图:

Gif图

示例 GIF图

接下来就是比较重要的一步, 实现播放, 去下载ijkplayer

下载完成之后, cd到当前文件夹下, 执行命令./init-ios.sh然后会等很久,等下载完成之后

我们cd 到 ios 文件夹执行命令./compile-ffmpeg.sh clean

再执行./compile-ffmpeg.sh all 编译时间较长。

完成之后打包IJKMediaFramework.framework框架

  1. 首先打开工程IJKMediaPlayer.xcodeproj, 位置如下图:
  2. 打开之后,去修改 scheme -> run -> release
  3. 模拟器下 command+b 编译下
  4. 真机下 command+b 编译
  5. 然后展开 Product 文件夹 如图 Show In Finder
  6. lipo -create “真机路径” “模拟器路径” -output “合并后的文件路径”注意合并后问价的命名,如下图
  7. 将Release-iphoneos里面的IJKMediaFramework删掉;
  8. 把刚生成的IJKMediaFramework拖到Release-iphoneos/IJKMediaFramework.framework/文件夹下
  9. 这样IJKMediaFramework.framework就是我们需要的了如图
  10. 将其添加到项目中并添加相关的库
Build Phases -> Link Binary with Libraries -> Add:
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 

播放代码

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(didChangeNotification:) name:IJKMPMoviePlayerPlaybackStateDidChangeNotification object:nil];

[self loadingImgView];

IJKFFOptions *options = [IJKFFOptions optionsByDefault];
// 硬解
//    [options setPlayerOptionIntValue:1  forKey:@"videotoolbox"];
//    // 帧速率(fps) (可以改,确认非标准桢率会导致音画不同步,所以只能设定为15或者29.97)
//    [options setPlayerOptionIntValue:29.97 forKey:@"r"];
//    // -vol——设置音量大小,256为标准音量。(要设置成两倍音量时则输入512,依此类推
//    [options setPlayerOptionIntValue:512 forKey:@"vol"];
// 去掉缓冲区
//    [options setPlayerOptionIntValue:0 forKey:@"packet-buffering"];

NSURL *url = [NSURL URLWithString:self.stream_addr];
IJKFFMoviePlayerController *playerVc = [[IJKFFMoviePlayerController alloc] initWithContentURL:url withOptions:options];
[playerVc prepareToPlay];
_playerVc = playerVc;
playerVc.view.frame = [UIScreen mainScreen].bounds;
[self.view addSubview:playerVc.view];

效果如图:

IJKMediaFramework已上传百度云

IJKMediaFramework.framework 下载 密码: uvha

推流篇

播放已完成, 那下面需要直播了, 直播首先在本地nginx搭建rtmp协议流媒体服务器;

  1. 安装Homebrew, 也就是在 OSX 上软件包管理工具;
  2. 如果没有安装输入以下命令安装ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”, 利用 man brew 可以查询本机是否安装;
  3. 安装完成之后, 那就利用 Homebrew 安装 nginx, 终端输入: brew tap homebrew/nginx;
  4. 下载nginx-rtmp-module模块, 执行命令brew install nginx-full –with-rtmp-module;
  5. 输入nginx, 打开http://localhost:8080, 如下图所示, 说明环境搭建好了;
  6. 修改配置文件, 先去查看 nginx 配置文件, 终端输入brew info nginx-full;
  7. 去搜索 Command+F 查找nginx.conf, 如图:
  8. 把路径复制下, finder前往文件夹, 粘贴路径找到配置文件, 用编辑器打开, 如图:
  9. 在上图位置添加如下代码:
rtmp {
    server {
        listen 1935;
        application rtmplive {
            live on;
            record off;
        }
    }
}

rtmp:协议名, server:服务器配置, listen:监听的端口号, application应用名称, live:开启实时; 配置完成之后记得刷新, 也就是重新加载配置文件 终端输入: nginx -s reload

以上推流就准备就绪, 但是由于是本地测试, 不在外网, 那想要查看自己直播, 需要一个播放器了; 去下载 VLC

音视频采集

下面就要去项目进行客户端采集音视频; 利用 pod 管理, Podfile 里 添加pod 'LFLiveKit' 即可, 当然你也可以不用 pod 直接添加也行; 并添加相关依赖库之后, 看代码实现:

- (LFLiveSession *)session {
if (!_session) {

    /***   默认分辨率368 * 640  音频:44.1 iphone6以上48  双声道  方向竖屏 ***/
    LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
    videoConfiguration.videoSize = CGSizeMake(360, 640);
    videoConfiguration.videoBitRate = 800*1024;
    videoConfiguration.videoMaxBitRate = 1000*1024;
    videoConfiguration.videoMinBitRate = 500*1024;
    videoConfiguration.videoFrameRate = 24;
    videoConfiguration.videoMaxKeyframeInterval = 48;
    videoConfiguration.outputImageOrientation = UIInterfaceOrientationPortrait;
    videoConfiguration.autorotate = NO;
    videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
    _session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:videoConfiguration captureType:LFLiveCaptureDefaultMask];

    _session.delegate = self;
    _session.showDebugInfo = NO;
    _session.preView = self;
    
    
}
return _session;
}		

开启音视频

- (void)requestAccessForVideo {
__weak typeof(self) _self = self;
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusNotDetermined: {
    // 许可对话没有出现,发起授权许可
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if (granted) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [_self.session setRunning:YES];
                });
            }
        }];
    break;
}
case AVAuthorizationStatusAuthorized: {
    // 已经开启授权,可继续
    dispatch_async(dispatch_get_main_queue(), ^{
        [_self.session setRunning:YES];
    });
    break;
}
case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
    // 用户明确地拒绝授权,或者相机设备无法访问

    break;
default:
    break;
	}
}

- (void)requestAccessForAudio {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
switch (status) {
case AVAuthorizationStatusNotDetermined: {
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
        }];
    break;
}
case AVAuthorizationStatusAuthorized: {
    break;
}
case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
    break;
default:
    break;
   }
}

相关状态代理方法

- (void)liveSession:(nullable LFLiveSession *)session liveStateDidChange:(LFLiveState)state {
NSLog(@"liveStateDidChange: %ld", state);
switch (state) {
case LFLiveReady:
    _stateLabel.text = @"准备中";
    break;
case LFLivePending:
    _stateLabel.text = @"连接中";
    break;
case LFLiveStart:
    _stateLabel.text = @"已连接";
    break;
case LFLiveError:
    _stateLabel.text = @"连接错误";
    break;
case LFLiveStop:
    _stateLabel.text = @"直播已结束";
    break;
default:
    break;
	}
}


- (void)liveSession:(nullable LFLiveSession *)session debugInfo:(nullable LFLiveDebug *)debugInfo {
NSLog(@"debugInfo uploadSpeed: %@", formatedSpeed(debugInfo.currentBandwidth, debugInfo.elapsedMilli));
}


- (void)liveSession:(nullable LFLiveSession *)session errorCode:(LFLiveSocketErrorCode)errorCode {
NSLog(@"errorCode: %ld", errorCode);
}

开始直播的操作

LFLiveStreamInfo *stream = [LFLiveStreamInfo new];
//  推流地址
stream.url = @"rtmp://192.168.1.115:1935/rtmplive/room";
[self.session startLive:stream];

url 里192.168.1.115 是电脑本机的地址, 1935 就是我们上面 Nginx 的第9步, listen就是监听的端口号, 填写正确即可开始直播; 打开本机服务器, 终端输入 nginx 这是进入我要直播界面是未连接的状态, 如图:

点击开始直播, 连接成功, 状态如图:

接下来就可以打开刚刚下载的 VLC 查看了; url输入我们本地地址