简介: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。这个流程我拆成三步:
- 配置
AVCaptureSession,添加视频输入和视频输出,设置videoSettings为kCVPixelFormatType_32BGRA; - 在
captureOutput回调里拿到CMSampleBuffer,通过CMSampleBufferGetImageBuffer获取CVPixelBuffer; - 将
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 直播推流的坑往往不是某个功能不工作,而是在长时间运行后暴露出来的资源泄漏和时序问题。提前压测,比上线后救火要省心太多。
本文还有配套的精品资源,点击获取