Skip to content
maverick edited this page Jul 18, 2016 · 8 revisions

#5 功能使用

当你要深入理解 SDK 的一些参数及有定制化需求时,可以从高级功能部分中查询阅读,以下小节无前后依赖。

##5.1 音视频编码配置

PLStreamingKit 中通过不同的 configuration 设置不同的编码配置信息,对应的有:

  • PLVideoStreamingConfiguration 视频编码配置
  • PLAudioStreamingConfiguration 音频编码配置

配置生效的时刻有两个:

  • PLStreamingSession init 时传递对应的 configuration
  • 在推流前、推流中、推流结束后调用 -reloadVideoConfiguration:-reloadAudioConfiguration: 重置 configuration

需要注意的是,通过 reload 方法重置 configuration 时,需要确保传递的 configuration 与当前 streamingSession 已经持有的不是一个对象。

###5.1.1 视频编码参数

当不确定视频编码具体的参数该如何设定时,你可以选择 SDK 内置的几种视频编码质量。

1.Quality 的对比

Quality FPS ProfileLevel Video BitRate(Kbps)
kPLVideoStreamingQualityLow1 12 Baseline 31 150
kPLVideoStreamingQualityLow2 15 Baseline 31 264
kPLVideoStreamingQualityLow3 15 Baseline 31 350
kPLVideoStreamingQualityMedium1 30 Baseline 31 512
kPLVideoStreamingQualityMedium2 30 Baseline 31 800
kPLVideoStreamingQualityMedium3 30 Baseline 31 1000
kPLVideoStreamingQualityHigh1 30 Baseline 31 1200
kPLVideoStreamingQualityHigh2 30 Baseline 31 1500
kPLVideoStreamingQualityHigh3 30 Baseline 31 2000

2.自定义编码参数

当前的 PLVideoStreamingConfiguration 中可自行设定的参数有:

  • videoProfileLevel
    • H.264 编码时对应的 profile level 影响编码压缩算法的复杂度和编码耗能。设置的越高压缩率越高,算法复杂度越高,相应的可能带来发热量更大的情况
  • videoSize
    • 编码的分辨率,对于采集到的图像,编码前会按照这个分辨率来做拉伸裁剪
  • videoFrameRate
    • 即 FPS,每一秒所包含的视频帧数
  • videoMaxKeyframeInterval
    • 两个关键帧的帧间隔,一般设置为 FPS 的三倍
  • videoBitRate
    • 平均的编码码率,设定后编码时的码率并不会是恒定不变,静物较低,动态物体会相应升高。

PLStreamingKit 为了防止编码参数设定失败而导致编码失败,出现推流无视频的情况,依据 videoProfileLevel 限定了其他参数的范围,该限定范围针对 Quality 生成的配置同样有效。参见以下表格:

ProfileLevel Max VideoSize Max FPS Max Video BitRate(Mbps)
Baseline 30 (720, 480) 30 10
Baseline 31 (1280, 720) 30 14
Baseline 41 (1920, 1080) 30 50
Main 30 (720, 480) 30 10
Main 31 (1280, 720) 30 14
Main 32 (1280, 1024) 30 20
Main 41 (1920, 1080) 30 50
High 40 (1920, 1080) 30 25
High 41 (1920, 1080) 30 62.5

3.码率、fps、分辨对清晰度及流畅度的影响

对于码率(BitRate)、FPS(frame per second)、分辨率(VideoSize)三者的关系,有必要在这里做一些说明,以便你根据自己产品的需要可以有的放矢的调节各个参数。

一个视频流个人的感受一般来说会有卡顿、模糊等消极的情况,虽然我们都不愿意接受消极情况的出现,但是在 UGC 甚至 PGC 的直播场景中,都不可避免的要面对。因为直播推流实时性很强烈,所以为了保证这一实时性,在网络带宽不足或者上行速度不佳的情况下,都需要做出选择。

要么选择更好的流程度但牺牲清晰度(模糊),要么选择更好的清晰度但牺牲流畅度(卡顿),这一层的选择大多由产品决定。

一般来说,当选定了一个分辨率后,推流过程中就不会对分辨率做变更,但可以对码率和 FPS 做出调节,从而达到上述两种情况的选择。

效果 码率 FPS
流畅度 负相关 正相关
清晰度 正相关 负相关

通过这个关联,我们就可以容易的知道该如何从技术层面做出调整。在追求更好的流畅度时,我们可以适当降低码率,如果 FPS 已经较高(如 30)时,可以维持 FPS 不变更,如果此时因为码率太低而画面无法接受,可以再适当调低 FPS;在追求更清晰的画质时,可以提高码率,FPS 调节至 24 左右人眼大多还会识别为流畅,如果可以接受有轻微卡顿,那么可以将 FPS 设置的更低,比如 20 甚至 15。

总之,这三者之间一起构建其了画面清晰和视频流畅的感觉,但最终参数是否能满意需要自己不断调整和调优,从而满足产品层面的需求。

###5.1.2 音频编码参数

相比于视频繁杂的参数,当前 PLAudioStreamingConfiguration 提供的参数较为简单,当前音频编码最终输出为 AAC-LC。

Quality 的对比:

Quality Audio BitRate(Kbps)
kPLAudioStreamingQualityHigh1 64
kPLAudioStreamingQualityHigh2 96
kPLAudioStreamingQualityHigh3 128

###5.1.3 切换音视频配置

为了满足推流中因网络变更,网络拥塞等情况下对码率、FPS 等参数的调节,PLStreamingKit 提供了重置编码参数的方法,因为在重置编码器时会重新发送编码参数信息,可能触发播放器重置解码器或者清除缓存的操作(依据播放器自身行为而定),所以推流中切换编码参数时,观看短可能出现短暂(但视觉可感知)的卡顿。因此建议不要频繁的切换编码参数,进而避免因此带来的播放端体验问题。

  • 在推流前、推流中、推流结束后调用 -reloadVideoConfiguration:-reloadAudioConfiguration: 重置 configuration

需要注意的是,通过 reload 方法重置 configuration 时,需要确保传递的 configuration 与当前 streamingSession 已经持有的不是一个对象。

###5.1.4 建议编码参数

提示:以下为建议值,可根据产品需求自行更改调节。

UGC 场景,因为主播方所在的网络环境参差不齐,所以不易将码率设置的过高,此处我们给出建议设定

  • WiFi: video Medium1 或者自定义编码参数时设定码率为 400~500Kbps
  • 3G/4G: video Low2 或者自定义编码参数时设定码率为 200~300Kbps

PGC 场景,因为主播方所在网络一般都会有较高的要求,并且主播网络质量大多可以保障带宽充足,此处我们给出建议设定

  • WiFi: video High1 或者自定义编码参数时设定码率为 1000~1200Kbps
  • 3G/4G: video Medium2 或者自定义编码参数时设定码率为 600~800Kbps

对于 PGC 中的 3G/4G 场景,假定 PGC 时会配备较好的外置热点保证上行带宽充足。

###5.1.5 如何只推音频

当你只需要推送音频时,并不需要额外的增加代码,只需要在创建 PLStreamingSession 时,只传入 PLAudioStreamingConfiguration 对象即可,这样 PLStreamingSession 就不会在内部创建视频编码的相关内容,推流时也只会发音频配置信息和音频数据。

##5.2 DNS 优化

在大陆一些地区或特别的运营商线路,存在较为普遍的 DNS 劫持问题,而这对与依赖 DNS 解析 rtmp 流地址的 PLStreamingKit 来说是很糟糕的情况,为了解决这一问题,我们引入了 HappyDNS 这个库,以便可以实现 httpDNS,localDNS 等方式解决这类问题。

###5.2.1 HappyDNS

你可以点击这里跳转到 HappyDNS 的 GitHub 主页,在那里查看更详细的介绍和使用。

默认情况下,你所创建的 PLStreamingSession 对象,内部持有一个 HappyDNS 对应的 manager 对象,来负责处理 DNS 解析。

如果你期望按照不同的规则来做 DNS 解析,那么你可以在创建 PLStreamingSession 前,创建好自己的 QNDnsManager 对象,我们在 PLStreamingSession 中提供了一个 init 方法满足这类需求,你可以传递自己的 QNDnsManager 对象给 PLStreamingSession,从而定制化 DNS 解析。

##5.3 流状态获取

PLStreamingKit 中,通过反馈 PLStreamingSession 的状态来反馈流的状态。我们定义了几种状态,确保 PLStreamingSession 对象在有限的几个状态间切换,并可以较好的反应流的状态。

状态名 含义
PLStreamStateUnknow 初始化时指定的状态,不会有任何状态会跳转到这一状态
PLStreamStateConnecting RTMP 流链接中的状态
PLStreamStateConnected RTMP 已连接成功时的状态
PLStreamStateDisconnecting RTMP 正常断开时,正在断开的状态
PLStreamStateDisconnected RTMP 正常断开时,已断开的状态
PLStreamStateError 因非正常原因导致 RTMP 流断开,如包发送失败、流校验失败等

###5.3.1 state 状态回调

state 状态对应的 Delegate 回调方法是:

- (void)streamingSession:(PLStreamingSession *)session streamStateDidChange:(PLStreamState)state;

只有在正常连接,正常断开的情况下跳转的状态才会触发这一回调。所谓正常连接是指通过调用 -startWithCompleted: 方法使得流连接的各种状态,而所谓正常断开是指调用 -stop 方法使得流断开的各种状态。所以只有以下四种状态会触发这一回调方法。

  • PLStreamStateConnecting
  • PLStreamStateConnected
  • PLStreamStateDisconnecting
  • PLStreamStateDisconnected

###5.3.2 error 状态回调

error 状态对应的 Delegate 回调方法是

- (void)streamingSession:(PLStreamingSession *)session didDisconnectWithError:(NSError *)error;

除了调用 -stop 之外的所有导致流断开的情况,都被归属于非正常断开的情况,此时就会触发该回调。对于错误的处理,我们不建议触发了一次 error 后就停掉,最好可以在此时尝试有限次数的重连,详见重连小节。

###5.3.3 status 状态回调

除了 state 作为流本身状态的切换,我们还提供了流实时情况的反馈接口。

- (void)streamingSession:(PLStreamingSession *)session streamStatusDidUpdate:(PLStreamStatus *)status;

默认情况下,该回调每隔 3s 调用一次,每次包含了这 3s 内音视频的 fps 和总共的码率(注意单位是 kbps)。你可以通过 PLStreamingSession 的 statusUpdateInterval 属性来读取或更改这个回调的间隔。

###5.3.4 产品层面的反馈

status 的状态回调可以很好的反应发送情况,及网络是否流畅,是否拥塞。所以此处可以作为产品层面对弱网情况决策的一个入口。

一般的,当 status.videoFPS 比预设的 FPS 明显小时(小于等于 20%),并且维持几秒都是如此,那么就可以判定为当前主播所在的网络为弱网环境,可以给主播视觉上的提示,或者主动帮她降低编码配置,甚至直接断掉主播的流,这些都由具体的产品需求而定,而此处只是给出一个入口的提示和建议。

##5.4 视频滤镜渲染

PLStreamingKit 因为只负责编码推流,所以采集和音视频预处理的权利都在开发者自己手中,可以做出更灵活的决定,此处就以视频滤镜为例说明如何配合其他第三方滤镜库实现实时的滤镜处理。

###5.4.1 GPUImage 接入

GPUImage 作为当前 iOS 平台使用率最高的图像渲染引擎,可以轻松与 PLStreamingKit 对接,利用 GPUImage 已有的 125 个内置滤镜满足大部分的直播滤镜需求。

###5.4.2 滤镜实例

// 使用 GPUImageVideoCamera 获取摄像头数据
GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;

// 创建一个 filter
GPUImageSketchFilter *filter = [[GPUImageSketchFilter alloc] init];

CGRect bounds = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(bounds);
CGFloat height = width * 640.0 / 480.0;
GPUImageView *filteredVideoView = [[GPUImageView alloc] initWithFrame:(CGRect){0, 64, width, height}];

// Add the view somewhere so it's visible
[self.view addSubview:filteredVideoView];

[videoCamera addTarget:filter];
[filter addTarget:filteredVideoView];

// 创建一个 GPUImageRawDataOutput 作为 filter 的 Target
GPUImageRawDataOutput *rawDataOutput = [[GPUImageRawDataOutput alloc] initWithImageSize:CGSizeMake(480, 640) resultsInBGRAFormat:YES];
[filter addTarget:rawDataOutput];
__weak GPUImageRawDataOutput *weakOutput = rawDataOutput;
__weak typeof(self) wself = self;
[rawDataOutput setNewFrameAvailableBlock:^{
    __strong GPUImageRawDataOutput *strongOutput = weakOutput;
    __strong typeof(wself) strongSelf = wself;
    [strongOutput lockFramebufferForReading];

    //从 GPUImageRawDataOutput 中获取 CVPixelBufferRef
    GLubyte *outputBytes = [strongOutput rawBytesForImage];
    NSInteger bytesPerRow = [strongOutput bytesPerRowInOutput];
    CVPixelBufferRef pixelBuffer = NULL;
    CVPixelBufferCreateWithBytes(kCFAllocatorDefault, 480, 640, kCVPixelFormatType_32BGRA, outputBytes, bytesPerRow, nil, nil, nil, &pixelBuffer);
    [strongOutput unlockFramebufferAfterReading];
    if(pixelBuffer == NULL) {
        return ;
    }

    // 发送视频数据
    [strongSelf.session pushPixelBuffer:pixelBuffer completion:^{
        CVPixelBufferRelease(pixelBuffer);
    }];
}];

[videoCamera startCameraCapture];

视频采集及素描滤镜部分的代码就这么多,完整的可运行代码在 GitHub 的 Example 中找到并尝试运行。

##5.5 网络异常处理

直播中,网络异常的情况比我们能意料到的可能会多不少,常见的情况一般有:

  • 网络环境切换,比如 3G/4G 与 Wi-Fi 环境切换
  • 网络不可达,网络断开属于这一类
  • 带宽不足,可能触发发送失败
  • 上行链路不佳,直接影响流发送速度

作为开发者我们不能乐观的认为只要是 Wi-Fi 网就是好的,因为即便是 Wi-Fi 也有可能因为运营商上行限制,共享网络带宽等因素导致以上网络异常情况的出现。

为何在直播中要面对这么多的网络异常情况,而在其他上传/下载中很少遇到的,这是因为直播对实时性的要求使得它不得面对这一情况,即无论网络是否抖动,是否能一直良好,直播都要尽可能是可持续,可观看的状态。

对于网络环境的切换,通常需要 App 整体做出调整,不单单是针对直播,所以 PLStreamingKit 并未对这一情况做额外的监听,而是需要开发者自己对这些状态做出处理。

###5.5.1 重连

PLStreamingKit 内部不包含重连逻辑。之所以不包含,主要因素是考虑到 App 的业务逻辑场景多样而负责,对于直播重连的次数,时机,间隔都会有不同的需求,而此时应该让开发者自己来决定是否重连,以及尝试重连的次数。

当因为网络异常而触发了推流断开时,会通过 error Delegate 回调触发。

- (void)streamingSession:(PLStreamingSession *)session didDisconnectWithError:(NSError *)error;

你可以在这个方法内通过重新调用 -startWithCompleted: 方法来尝试重连。此处建议不要立即重连,而是采用重连间隔加倍的方式,比如共尝试 3 次重连,第一次等待 0.5s, 第二次等待 1s, 第三次等待 2s,这样的方式主要考虑到弱网时网络带宽的缓解需要时间,而加倍重连可以更容易在网络恢复的时候连接,而非在网络已经拥塞时还不断做无用功的重连。

当网络从 3G/4G 切换到 Wi-Fi 后,基于节省流量等需求考虑,你可能需要进行一次快速的重连,使得数据可以通过 Wi-Fi 网络发送,这时,可以调用 -restartWithCompleted: 方法来快速重连。