玩转直播学习全套件

最近学习的总结!
首先来介绍下简单的推流.
iOS 直播 —— 推流
推流,就是将采集到的音频,视频数据通过流媒体协议发送到流媒体服务器。
• 推流前的工作:采集,处理,编码压缩
• 推流中做的工作: 封装,上传

推流前的工作:

采集到的音视频数据通过流媒体协议发送到流媒体服务器上:

其实有一个库 LFLiveKit 已经实现了 后台录制、美颜功能、支持h264、AAC硬编码,动态改变速率,RTMP传输等,我们真正开发的时候直接使用就很方便啦。

•   LiveVideoCoreSDK : 实现了美颜直播和滤镜功能,我们只要填写RTMP服务地址,直接就可以进行推流啦。
•   PLCameraStreamingKit: 也是一个不错的 RTMP 直播推流 SDK。

但还是推荐用 LFLiveKit。

一、采集视频
AVCaptureVideoDataOutput *videoOutput; //视频采集
AVCaptureAudioDataOutput *audioOutput; //音频采集

二、GPUImage 处理

在进行编码 H.264 之前,一般来说肯定会做一些美颜处理的,否则那播出的感觉太真实,就有点丑啦,在此以磨皮和美白为例简单了解。(具体参考的是:琨君 基于 GPUImage 的实时美颜滤镜)

直接用 BeautifyFaceDemo 中的类 GPUImageBeautifyFilter, 可以对的图片直接进行处理:
GPUImageBeautifyFilter *filter = [[GPUImageBeautifyFilter alloc] init];
UIImage *image = [UIImage imageNamed:@"testMan"];
UIImage *resultImage = [filter imageByFilteringImage:image];
self.backgroundView.image = resultImage;

但是视频中是怎样进行美容处理呢?怎样将其转换的呢?平常我们这样直接使用:
GPUImageBeautifyFilter *beautifyFilter = [[GPUImageBeautifyFilter alloc] init];
[self.videoCamera addTarget:beautifyFilter];
[beautifyFilter addTarget:self.gpuImageView];
此处用到了 GPUImageVideoCamera,可以大致了解下 GPUImage详细解析(三)- 实时美颜滤镜:

三、视频、音频压缩编码

而编码是用 硬编码呢 还是软编码呢? 相同码率,软编图像质量更清晰,但是耗电更高,而且会导致CPU过热烫到摄像头。不过硬编码会涉及到其他平台的解码,有很多坑。综合来说,iOS 端硬件兼容性较好,iOS 8.0占有率也已经很高了,可以直接采用硬编。

硬编码:下面几个DEMO 可以对比下,当然看 LFLiveKit 更直接。
• VideoToolboxPlus
• iOSHardwareDecoder
• -VideoToolboxDemo
• iOS-h264Hw-Toolbox

我直接使用了 LFLiveKit ,里面已经封装的很好啦,此处对 Audiotoolbox && VideoToolbox 简单了解下:
1.AudioToolbox
iOS使用AudioToolbox中的AudioConverter API 把源格式转换成目标格式, 详细可以看 使用iOS自带AAC编码器。
// 1、根据输入样本初始化一个编码转换器
AudioStreamBasicDescription 根据指定的源格式和目标格式创建 audio converter
// 2、初始化一个输出缓冲列表 outBufferList
// 3、获取 AudioCallBack
OSStatus inputDataProc(AudioConverterRef inConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
// 4、音频格式完成转换
AudioConverterFillComplexBuffer 实现inBufferList 和 outBufferList、inputDataProc音频格式之间的转换。

2.VideoToolbox
iOS8之后的硬解码、硬编码API,此处只做编码用。
• // 1、初始化 VTCompressionSessionRef
• - (void)initCompressionSession;
• // 2、传入 解码一个frame
• VTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, (__bridge CFDictionaryRef)properties, (__bridge_retained void *)timeNumber, &flags);
• // 3、回调,处理 取得PPS和SPS
• static void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
• // 4、完成编码,然后销毁session
• VTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);
• VTCompressionSessionInvalidate(compressionSession);
• CFRelease(compressionSession);
• compressionSession = NULL;

四、推流

4-1、封装数据成 FLV,通过 RTMP 协议打包上传,从主播端到服务端即基本完成推流。

FLV流媒体格式是一种新的视频格式,全称为FlashVideo。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等缺点。
iOS 中的使用:详细看看 LFLiveKit 中的 LFStreamRTMPSocket 类。

4-2、RTMP
从推流端到服务端,数据经过处理后,最常用的协议是RTMP(Real Time Messaging Protocol,实时消息传送协议)。

RTMP的传输延迟通常在1-3秒,符合手机直播对性能的要求,因此RTMP是手机直播中最常见的传输协议。

但是网络延迟和阻塞等问题的一直存在的,所以通过Quality of Servic一种网络机制将流数据推送到网络端,通过CDN分发是必要的。

另外,服务端还需要对数据流一定的处理,转码,使得数据流支持HLS,HTTP-FLV,RTMP等格式的拉流,支持一转多,适配不同网络、分辨率的终端。(当然这就是服务端要做的事情啦)

可以用 LFLiveKit 直接尝试,或者也可以看看 LMLiveStreaming,当然此处先用一个本地视频推送尝试一下。

4-3、本地模拟推流
此处是跟着 快速集成iOS基于RTMP的视频推流 来实现的,否则就连基本的展示都不能啦啊。此处也可以配合着Mac搭建nginx+rtmp服务器 来安装,安装好 nginx 之后,安装ffmpeg、下载 VLC 就可以直接开始啦。

起初在用 ffmpeg 的时候,遇到下面那个错:

后来发现是自己输入错了,还是要仔细:
视频文件地址:/Users/qiu/Desktop/kobe.mp4(自己的一个测试视频)
推流拉流地址:rtmp://localhost:1935/rtmplive/room
~ ffmpeg -re -i /Users/qiu/Desktop/kobe -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/rtmplive/room
那个-vcodec libx264 -acodec aac -strict -2 -f flv命令也不要写错了,ffmpeg 命令可参考 FFmpeg常用基本命令。

4-4、手机直播 - VLC上 显示
为了更好的感受下,我们可以直接 用 LMLiveStreaming,然后打开 VLC 中 的 file -- Open Network, 直接输入代码中的 url:

另外其实好多第三方的集成也很好用,可参考
• 七牛云
• 腾讯的直播 LVB
• 网易云信 SDK
• 趣拍云

备注参考:
• LiveVideoCoreSDK
• LFLiveKit
• GPUImage
• LMLiveStreaming
• PLCameraStreamingKit
• iOS手机直播Demo技术简介
• iOS视频开发经验
• iOS 上的相机捕捉
• CMSampleBufferRef 与 UIImage 的转换
• GPUImage详细解析(三)- 实时美颜滤镜
• iOS8系统H264视频硬件编解码说明
• 利用FFmpeg+x264将iOS摄像头实时视频流编码为h264文件
• 使用VideoToolbox硬编码H.264
• 使用iOS自带AAC编码器
• 如何搭建一个完整的视频直播系统?
• 直播中累积延时的优化
• 使用VLC做流媒体服务器(直播形式)
配置完成后,
一. 下载ijkplayer
ijkplayer下载地址:https://github.com/Bilibili/ijkplayer
下载完成后解压.

二. 编译 ijkplayer
说是编译 ijkplayer, 其实是编译 ffmpeg, 在这里我们已经下载好了ijkplayer, 所以 github 上README.md中的Build iOS那一步中有一些步骤是不需要的.
下面开始一步一步编译:
1.打开终端, cd 到jkplayer-master文件夹中, 也就是下载完解压后的文件夹, 如下图:

2.执行命令行./init-ios.sh, 这一步是去下载 ffmpeg 的, 时间会久一点, 耐心等一下.

3.在第2步中下载完成后, 执行cd ios, 也就是进入到 ios目录中, 如下图:

4.进入 ios 文件夹后, 在终端依次执行./compile-ffmpeg.sh clean和./compile-ffmpeg.sh all命令, 编译 ffmpeg, 也就是README.md中这两步, 如下图:

编译时间较久, 耐心等待一下.

三. 打包IJKMediaFramework.framework框架
集成 ijkplayer 有两种方法:
一种方法是按照IJKMediaDemo工程中那样, 直接导入工程IJKMediaPlayer.xcodeproj, 在这里不做介绍, 如下图:

第二种集成方法是把 ijkplayer 打包成framework导入工程中使用. 下面开始介绍如何打包IJKMediaFramework.framework, 按下面步骤开始一步一步做:
1. 首先打开工程IJKMediaPlayer.xcodeproj, 位置如下图:

打开后是这样的, 如下图:

2.工程打开后设置工程的 scheme, 具体步骤如下图:

3.设置好 scheme 后, 分别选择真机和模拟器进行编译, 编译完成后, 进入 Finder, 如下图:

进入 Finder 后, 可以看到有真机和模拟器两个版本的编译结果, 如下图:

下面开始合并真机和模拟器版本的 framework, 注意不要合并错了, 合并的是这个文件, 如下图:

打开终端, 进行合并, 命令行具体格式为:
lipo -create "真机版本路径" "模拟器版本路径" -output "合并后的文件路径"

合并后如下图:

下面很重要, 需要用合并后的IJKMediaFramework把原来的IJKMediaFramework替换掉, 如下图, 希望你能看懂:

上图中的1、2两步完成后, 绿色框住的那个IJKMediaFramework.framework文件就是我们需要的框架了, 可以复制出来, 稍后我们需要导入工程使用.

四. iOS工程中集成ijkplayer
新建工程, 导入合并后的IJKMediaFramework.framework以及相关依赖框架以及相关依赖框架,如下图:

导入框架后, 在ViewController.m进行测试, 首先导入IJKMediaFramework.h头文件, 编译看有没有错, 如果没有错说明集成成功.

代码见下
IJKFFMoviePlayerController 注册通知中心

  • (void)loadStateDidChange:(NSNotification*)notification {
    IJKMPMovieLoadState loadState = _player.loadState;

    ////状态为缓冲几乎完成,可以连续播放
    if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
    NSLog(@"LoadStateDidChange: IJKMovieLoadStatePlayThroughOK: %d\n",(int)loadState);
    }
    ////缓冲中
    else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
    /*
    这里主播可能已经结束直播了。我们需要请求服务器查看主播是否已经结束直播。
    方法:
    1、从服务器获取主播是否已经关闭直播。
    优点:能够正确的获取主播端是否正在直播。
    缺点:主播端异常crash的情况下是没有办法通知服务器该直播关闭的。
    2、用户http请求该地址,若请求成功表示直播未结束,否则结束
    优点:能够真实的获取主播端是否有推流数据
    缺点:如果主播端丢包率太低,但是能够恢复的情况下,数据请求同样是失败的。
    */
    NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\n", (int)loadState);

    } else {
    NSLog(@"loadStateDidChange: ???: %d\n", (int)loadState);
    }
    }

  • (void)moviePlayBackFinish:(NSNotification*)notification {
    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(@"mediaIsPrepareToPlayDidChange\n");
    }

  • (void)moviePlayBackStateDidChange:(NSNotification*)notification {

if 0

//    NSLog(@"%@",notification);
//    IJKMPMoviePlaybackStateStopped,        停止
//    IJKMPMoviePlaybackStatePlaying,        正在播放
//    IJKMPMoviePlaybackStatePaused,         暂停
//    IJKMPMoviePlaybackStateInterrupted,    打断
//    IJKMPMoviePlaybackStateSeekingForward, 快进
//    IJKMPMoviePlaybackStateSeekingBackward 快退


switch (self.player.playbackState) {
    case IJKMPMoviePlaybackStateStopped:
        NSLog(@"停止");
        break;
    case IJKMPMoviePlaybackStatePlaying:
        NSLog(@"正在播放");
        break;
    case IJKMPMoviePlaybackStatePaused:
        NSLog(@"暂停");
        break;
    case IJKMPMoviePlaybackStateInterrupted:
        NSLog(@"打断");
        break;
    case IJKMPMoviePlaybackStateSeekingForward:
        NSLog(@"快进");
        break;
    case IJKMPMoviePlaybackStateSeekingBackward:
        NSLog(@"快退");
        break;
    default:
        break;
}

endif

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;
    }
}

}

pragma Install Notifiacation

  • (void)installMovieNotificationObservers {

    /*
    IJKFFMoviePlayerController 支持的通知有很多,常见的有:

    IJKMPMoviePlayerLoadStateDidChangeNotification(加载状态改变通知)
    IJKMPMoviePlayerPlaybackDidFinishNotification(播放结束通知)
    IJKMPMoviePlayerPlaybackStateDidChangeNotification(播放状态改变通知)
    */

    //监听加载状态改变通知
    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(loadStateDidChange:)
    name:IJKMPMoviePlayerLoadStateDidChangeNotification
    object:_player];
    //播放结束通知
    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(moviePlayBackFinish:)
    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];

}

  • (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];

}

注意:在iOS8以上建议开启硬件码:
//videotoolbox 配置(硬件解码)
[options setPlayerOptionIntValue:1 forKey:@"videotoolbox"];

你可能感兴趣的:(玩转直播学习全套件)