简介:PLCameraStreamingKit 是 Pili 直播 SDK 的 iOS 推流端,一个带采集模块的开源老版本,面向需要快速搭建 RTMP 直播推流能力的移动开发者。该版本支持 H.264 与 AAC 编码,硬编、软编均可运行,集成美颜、背景音乐、水印等直播场景功能,并提供丰富的数据与状态回调,便于按业务二次封装。压缩包共 594 个文件、约 4.97MB,以 355 个 h 头文件、101 个 md 文档、65 个 m 源文件与 27 个 c 文件为主体,另有少量静态库、工程配置、崩溃上报代码,兼顾接口声明、使用说明和底层实现。整套源码与预编译库搭配,目录结构清晰,既能快速嵌入现有工程,也适合深入研究采集、编码、推流、异常恢复等模块的协作方式;入门者可借助文档梳理推流链路,进阶者可根据回调与配置项做软硬编切换、美颜和水印等定制开发。已有 638 人学习下载,对关注 iOS 直播开发的人群有务实参考价值。 iOS 上做直播推流,RTMP 协议和推流 SDK 几乎是绕不开的两个词。这几年从直播答题到教育双师,从电商带货到户外连麦,我见过太多团队拿着一份压缩包就开始集成,结果被采集卡顿、延迟漂移、断流重连搞得焦头烂额。这篇博文就围绕“iOS RTMP 直播推流 SDK”这个主题,把选型思路、核心代码、参数调优和排坑经验一次性讲透,适合正在做 iOS 直播功能、或者准备自研推流模块的开发者参考。

先说结论:RTMP 虽然老,但在国内直播场景下依然是最稳的推流协议,没有之一。你要做的不是重复造轮子,而是搞懂一个成熟 SDK 的关键节点,然后把业务层做厚。下面直接进入正文。

1. 项目背景与整体方案选型

1.1 为什么 iOS 直播推流还是绕不开 RTMP

很多人一上来就问“RTMP 是不是过时了,为什么不用 WebRTC 或者 SRT”。我理解这种想法,但放到真实业务里,RTMP 的江湖地位依然稳固。核心原因有三个:第一,RTMP 基于 TCP,传输可靠,弱网环境下不会像 UDP 系协议那样出现大范围花屏和丢帧;第二,服务端生态成熟,从 Nginx-RTMP 到腾讯云、阿里云的直播服务,全部原生支持 RTMP 推流接入,你拿到一个 rtmp:// 地址就能跑通全链路;第三,播放端兼容性极好,Flash 虽然没了,但 HLS 转封装、FLV 播放方案已经非常成熟,CDN 厂商对 RTMP 上行链路的优化也做得最透。

做 iOS 直播推流 SDK,本质上就是围绕 RTMP 做三件事:采集、编码、传输。采集端拿到摄像头和麦克风的数据,编码器压缩成 H.264 视频流和 AAC 音频流,然后通过 RTMP 协议封装成 FLV 标签发到服务器。这个过程听起来简单,但每个环节都有大量细节,尤其是 iOS 系统对后台采集、屏幕采集、硬编硬解的权限和生命周期限制非常多,原生 AVFoundation 框架虽然有现成接口,但直接拿来做生产级推流远远不够。

1.2 自研还是集成 SDK,主流方案怎么选

我见过不少团队一开始雄心勃勃想自研推流 SDK,最后都灰溜溜换成了成熟方案。自研的最大坑不是编码,而是长期维护成本。iOS 系统每年升级,Camera 权限策略在变,AAC 编码器的参数行为在变,App 后台策略也在变,这些都需要有人持续跟进。如果你的核心业务不是直播底层技术,我建议优先集成成熟 SDK,把精力放在业务层。

目前 iOS 上主流的 RTMP 推流 SDK 有这几类:一是商业 SDK,功能全、文档好,但要收费且受平台政策约束;二是开源 SDK,典型代表是 LFLiveKit,代码结构清晰,适合二次开发,但停止维护很久,需要自己适配新系统;三是基于系统 VideoToolbox 和 AudioToolbox 自研轻量推流器。我的建议是:MVP 阶段直接用开源的 LFLiveKit 跑通流程,中期根据业务需求对采集模块做替换,后期如果量大了再考虑自研传输层。下面这张表可以帮你快速定位:

方案 成本 可控性 维护难度 适用阶段
商业SDK(七牛/声网/腾讯) 高(按量付费) 低 低 快速上线的产品
开源SDK(LFLiveKit等) 低 中 中 中小型项目、自研基础
完全自研 极高 高 极高 大厂、核心业务自控

我自己的实践路径是先用开源 SDK 做了两个版本,踩完所有坑之后,把采集和编码部分替换成了自研代码,传输层仍然用 RTMP。这样既保证了上线速度,又能在出问题时快速定位。

2. 推流 SDK 核心技术点拆解

2.1 采集端:分辨率、帧率、码率如何配比

采集参数不是随便填的,它直接影响画质、延迟和性能三者的平衡。我见过有人把分辨率拉到 1080p、码率开到 6000kbps,结果中端 iPhone 发热严重,帧率掉到 20fps,画面还不如 720p 流畅。这里的核心逻辑是:码率决定画质上限,帧率决定流畅度,分辨率决定细节量,三者要匹配。

实际项目中,我常用的配比方案是:直播场景推荐 720p 分辨率、30fps 帧率、码率 1500~2500kbps;如果做游戏直播或屏幕录制,画面静态区域多,可以适当降低码率;如果做户外运动直播,画面变化剧烈,码率需要适当上调。具体码率可以参考公式:码率(kbps) ≈ 宽度 × 高度 × 帧率 × 0.07~0.1。比如 1280×720×30×0.08 ≈ 2211kbps,这个值在大多数直播场景下画质和带宽的平衡都还不错。

另外,iOS 采集端要关注 AVCaptureSession 的预设值。不要直接设 AVCaptureSessionPresetHigh 这种笼统的值,建议手动指定宽度和高度,用 AVCaptureSessionPreset1280x720 ,同时开启 videoSettings 中的关键帧间隔设置。关键帧间隔(GOP)通常设置为帧率的 2 倍,也就是 60 帧一个关键帧,这样秒开和延迟都能兼顾。

2.2 编码器:H.264 硬编与软编的取舍

iOS 上视频编码有两条路:VideoToolbox 硬编和 x264 软编。硬编的优势是功耗低、速度快,缺点是码率控制不够精细,某些系统版本会有花屏问题;软编的优势是参数可控、画质好,缺点是 CPU 占用高,中低端设备很容易发热降频。

实际项目里,我推荐默认走 VideoToolbox 硬编,同时加一个开关,允许用户在设置页切换软编。原因很简单:硬编是 iOS 系统的原生能力,集成成本低,而且从 iPhone 6s 开始,硬编质量已经非常稳定。实现硬编只需要三步:创建 VTCompressionSession ,设置 kVTCompressionPropertyKey_ProfileLevel 为 kVTProfileLevel_H264_High_AutoLevel ,设置码率和帧率,然后通过 VTCompressionSessionEncodeFrame 喂入像素缓冲区。

有一个容易被忽略的坑:硬编时一定要设置 kVTCompressionPropertyKey_RealTime 为 true ,否则编码器会为了追求压缩率引入几百毫秒的延迟。另外, kVTCompressionPropertyKey_MaxKeyFrameInterval 设置的是帧数而不是秒数,很多人在这里踩坑,以为设了 2 就是两秒一个关键帧,实际上是两帧一个关键帧,浪费带宽。

音频编码方面,AAC 是直播标配。iOS 上可以用 AudioToolbox 的 AudioConverter 将 PCM 转成 AAC,注意设置采样率 44100Hz、声道数 1(直播场景单声道足够)、码率 128kbps 左右即可。如果要做连麦,再考虑双声道和更高码率。

2.3 传输层:RTMP 握手与 FLV 封装细节

RTMP 传输层的核心是握手和 FLV 封装。握手是三个固定长度的块,客户端先发 C0、C1,服务器回 S0、S1、S2,客户端再发 C2,完成之后就可以收发命令消息了。很多人写推流 SDK 时直接用第三方库(如 librtmp)跳过握手细节,但你要排查问题时还是得理解这个过程。

FLV 封装相对简单,视频标签和音频标签交替发送,每个标签由 11 字节头部和负载组成。头部包含标签类型(8 为音频,9 为视频,18 为脚本数据)、数据长度、时间戳和流 ID。时间戳是毫秒为单位,前一个字节存低 8 位,后三个字节存高 24 位,很多初级开发者在这里搞错字节序,导致播放端时间轴错乱。

我建议传输层直接使用成熟的 librtmp 库封装,不要自己实现 RTMP 协议细节,但必须理解协议结构。实际开发中,把 librtmp 编译成静态库链接进 iOS 工程,然后通过 RTMP_Connect 、 RTMP_ConnectStream 、 RTMP_Write 三个关键函数完成推流。 RTMP_Write 需要把编码后的 H.264 裸流先封装成 FLV 标签再写入,这一层逻辑必须自己实现。

3. 集成与实操:从零构建推流 Demo

3.1 工程配置与依赖引入

假设你用 LFLiveKit 作为基础版本,第一步是引入依赖。我建议用 CocoaPods 管理,Podfile 里加一行:

pod 'LFLiveKit'

然后 pod install 。这里有个小坑,LFLiveKit 在 iOS 14 以上会出现权限描述不完整的问题,需要在 Info.plist 里补充 NSCameraUsageDescription 和 NSMicrophoneUsageDescription ,否则摄像头和麦克风直接崩溃。

如果你打算替换成自研采集和编码,可以不引入完整 SDK,只引入 librtmp 静态库。编译 librtmp 的时候要注意架构支持,用 Xcode 12 以上版本编译时,需要排除 armv7s 架构,否则会报错。推荐直接用脚本编译 arm64 和 x86_64 双架构,方便模拟器调试。

3.2 推流流程核心代码

以 LFLiveKit 为例,核心推流代码可以这样写:

import LFLiveKit

class LivePusherManager: NSObject {
    private lazy var session: LFLiveSession = {
        let audioConfig = LFLiveAudioConfiguration.default()
        let videoConfig = LFLiveVideoConfiguration.defaultConfiguration(
            quality: .medium3, // 720p, 30fps, 2Mbps
            outputImageOrientation: .portrait
        )
        let session = LFLiveSession(audioConfiguration: audioConfig,
                                    videoConfiguration: videoConfig)!
        session.delegate = self
        session.captureDevicePosition = .front
        return session
    }()

    func startPush(urlString: String) {
        let stream = LFLiveStreamInfo()
        stream.url = urlString
        session.startLive(with: stream)
    }

    func stopPush() {
        session.stopLive()
    }
}

用系统原生 AVFoundation 也可以实现类似效果,但需要自己管理 AVCaptureSession 的输出回调,然后把 CMSampleBuffer 转成 CVPixelBuffer,再喂给 VideoToolbox。这个流程我拆成三步:

  1. 配置 AVCaptureSession ,添加视频输入和视频输出,设置 videoSettings 为 kCVPixelFormatType_32BGRA ;
  2. 在 captureOutput 回调里拿到 CMSampleBuffer ,通过 CMSampleBufferGetImageBuffer 获取 CVPixelBuffer ;
  3. 将 CVPixelBuffer 和音频 CMSampleBuffer 分别送入编码器。

如果你用 LFLiveKit,它内部已经封装好了这个流程,你只需要处理生命周期和推流地址。

3.3 参数配置与性能调优

参数配置的核心目标是延迟和画质的平衡。我实测下来,RTMP 推流 + HLS 播放的正常延迟在 3~5 秒,如果要做低延迟直播,需要配合播放端的 FLV 方案,延迟可以压到 1~2 秒。具体在 SDK 层可以做几个优化:

第一,关闭 VideoToolbox 的 B 帧。B 帧虽然能提升压缩率,但会引入额外的编码延迟和播放端解码复杂度。在 VTCompressionSession 里设置 kVTCompressionPropertyKey_AllowFrameReordering 为 false ,强制编码器不产生 B 帧,延迟能降低 300ms 左右。

第二,调整 GOP 大小。关键帧间隔越大,码率越平稳,但拉流端首帧等待越久。推荐 GOP 设为帧率的 2 倍,即 60 帧,这样播放端最多等 2 秒就能出画面。

第三,开启码率自适应。iOS 上可以通过监控当前 buffer 的堆积情况动态调整码率。简单做法是在编码器回调里记录 CMSampleBuffer 的时间戳,如果发现解码端滞后超过 500ms,就把码率降一档;如果连续 5 秒没有丢帧,再升一档。这样在弱网环境下能显著减少卡顿。

音频参数方面,AAC 编码建议使用 kAudioFormatMPEG4AAC ,比特率根据场景选 96kbps 到 128kbps,采样率 44100Hz。连麦场景需要开启 AVAudioSession 的 kAudioSessionProperty_OtherAudioAvailable ,否则其他 App 的音频会被打断。

4. 实际踩坑与问题排查实录

4.1 延迟异常增大怎么排查

推流 SDK 上线后反馈最多的问题就是延迟越来越大。我遇到过一次,推流 30 分钟后播放端延迟从 3 秒涨到 10 秒,最开始怀疑是网络问题,后来查了服务端日志,发现是客户端发送速度比服务器消费速度快,导致服务器缓冲堆积。

这种问题的排查思路是:先看客户端本地是否堆积。在 RTMP 写入前加一个队列,每写入一个 FLV 标签就记录队列长度,如果队列长度持续增长,说明是采集或编码速度跟不上,或者网络发送阻塞。我常用的手段是在统计面板打点,每 5 秒输出一次队列长度、当前码率、编码帧率三项数据,一旦发现队列长度持续大于 30 帧,就主动丢帧。

另外注意时间戳的生成规则。RTMP 时间戳必须用采集时的 CMSampleBuffer 原始时间戳,而不是当前发送时间。如果用了发送时间,一旦网络抖动,时间戳会跳变,播放端会认为画面卡住,触发追帧或快进,延迟瞬间飙升。

4.2 内存暴涨和发热问题

iOS 推流 SDK 里内存暴涨一般有两个元凶:一是采集端没有及时释放 CMSampleBuffer ,导致视频帧堆积;二是编码器回调队列和主线程之间没有做好同步,造成并发访问冲突。

我踩过的坑是 AVCaptureSession 的 captureOutput 回调默认在串行队列,如果你在这个回调里做耗时操作(比如转格式),就会阻塞采集,导致帧率下降。正确做法是回调里只做轻量处理,立即把 CMSampleBuffer 转到自建的并发队列里做编码。同时注意,在 iOS 17 及以上系统,后台采集权限收得更紧,App 切到后台时一定要停止推流,否则会被系统直接杀掉。

发热问题主要看 CPU 占用。用硬编时 CPU 占用一般在 20% 以下,如果超过 40%,大概率是编码配置不对,比如没有设置 RealTime 属性或者 B 帧开启导致编码器性能下降。另外,推流时不要同时开太多后台任务,尤其是 CoreImage 滤镜,GPU 占用过高会直接拖垮编码性能。

4.3 断流重连策略怎么设计

直播推流里断流是常态,网络切换、服务器重启、App 被挂起都可能导致断流。重连策略设计得不好,用户就会看到黑屏或卡住的画面。

我推荐用“渐进式重连 + 手动重推”的组合。具体参数是:断流后立刻重连一次,失败后等 2 秒重连,再失败等 5 秒,再失败等 10 秒,最多重连 5 次。如果 5 次都失败,就不再自动重连,提示用户手动操作。重连时需要重新走一遍 RTMP 握手流程,并且把编码器重置,否则有些硬编 session 在断网时会残留错误状态。

另外一个容易忽略的点:断流重连成功后,要主动发一个关键帧。因为在断流期间播放端可能已经丢失了解码上下文,如果继续发 P 帧,播放端会花屏很久。在重连成功发送的第一个视频帧上,要把 kVTEncodeFrameOptionKey_ForceKeyFrame 设为 true ,强制编码器生成关键帧。

4.4 常见问题速查表

现象 可能原因 解决方案
推流成功但播放黑屏 关键帧间隔太大或首帧不是IDR 设置GOP为帧率的2倍,重连后强制发关键帧
画面卡顿但CPU不高 采集帧率不足或网络拥塞 检查camera配置,开启码率自适应
声音和画面不同步 音视频时间戳不一致 统一用采集时间戳,不要用发送时间
弱网下延迟无限增长 发送队列堆积 主动丢帧,降低码率,加快关键帧节奏
切后台后崩溃 系统回收摄像头资源 监听UIApplicationDidEnterBackgroundNotification,主动停止推流
硬编花屏 编码器session状态错误 重连时重建VTCompressionSession

这些坑我在实际项目里基本都踩过一轮,尤其时间戳和重连后的关键帧问题,几乎每个做推流的人都会遇到。如果你正在集成这类 SDK,建议先跑一遍上面的排查清单,能省不少时间。

最后再分享一个我自己的习惯:每次推流 SDK 上线前,我会专门写一个稳定性测试脚本,用固定的 rtmp 测试地址连续推流 12 小时,同时记录帧率、码率、CPU、内存和延迟五项指标。iOS 直播推流的坑往往不是某个功能不工作,而是在长时间运行后暴露出来的资源泄漏和时序问题。提前压测,比上线后救火要省心太多。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐