在实时音视频应用中,macOS 过去更多被视为“观看端”或“管理端”:用于查看摄像头画面、导播预览、监控墙展示、设备管理和流媒体调试。
但随着远程办公、在线教育、工业视觉、无人机图传、设备远程运维和软件操作演示等场景不断增加,Mac 正在从单纯的播放终端,逐渐变成实时音视频链路中的采集端、编码端和分发端。
这意味着,Mac 不仅需要播放 RTSP、RTMP 等实时流,还需要完成:
- 摄像头和麦克风采集;
- 屏幕或指定窗口采集;
- 系统声音采集;
- H.264/H.265 视频编码;
- AAC 音频编码;
- RTMP 低延迟推流;
- 局域网 RTSP 实时分发;
- 本地录像、快照和事件状态管理。
这正是大牛直播SDK(SmartMediaKit)推出 macOS 版推流能力和轻量级 RTSP 服务的核心背景。
本文结合 SmartMacPublisher 示例工程,从代码和工程设计角度,分析 macOS 为什么需要专业的低延迟 RTMP 推流与轻量级 RTSP 服务,以及这一方案适合哪些业务场景。
一、为什么 macOS 也需要专业推流SDK?
很多人提到推流,首先想到的是手机直播、摄像头推流或者 Windows 桌面采集。
但在大量专业场景中,Mac 已经不只是一个内容消费设备。
例如:
- 老师使用 Mac 播放课件,并把屏幕和讲解声音推送到教学平台;
- 工程师采集工业控制软件窗口,供远程专家实时查看;
- 售后人员将 Mac 上的软件操作过程推送给客户;
- 导播人员把 Mac 上的画面作为一路信号源接入直播系统;
- 开发者使用 Mac 快速产生 RTMP、RTSP 测试流;
- 局域网内临时将 Mac 摄像头或屏幕共享给多个播放终端;
- 会议、培训和产品发布过程中,同时推流并进行本地录像。
这些需求看似不同,底层却都离不开同一套实时音视频能力:
采集、格式处理、编码、封装、推送、分发、录像、状态回调和异常恢复。
如果从零开始开发,需要处理的问题非常多:
- 摄像头、麦克风设备枚举与权限申请;
- 屏幕、窗口和系统声音采集;
- BGRA、NV12、PCM 等媒体格式转换;
- VideoToolbox 编码器配置;
- RTMP 连接、发送、断线和重连;
- RTSP 服务端口、流名称和客户端会话管理;
- 动态分辨率和窗口尺寸变化;
- 音视频时间戳和同步;
- 推流、录像、RTSP 发布之间的状态组合;
- App 退出时的线程、采集设备和 SDK 资源释放。
任何一个环节处理不当,都可能带来黑屏、花屏、无声音、延迟堆积、摄像头无法释放或者应用退出崩溃等问题。
SmartMediaKit macOS 版的价值,并不只是“增加一个平台”,而是把这些复杂、容易踩坑的底层能力沉淀到 SDK 中,让上层应用更专注于采集源选择、业务流程、用户界面和产品体验。
二、SmartMacPublisher的总体设计:上层采集,SDK负责媒体处理与输出
SmartMacPublisher 没有简单照搬移动端“SDK 内置采集”的模式,而是采用了更适合 macOS 桌面应用的架构:
上层应用负责采集,SmartPublisherSDK 负责编码、封装、推流、RTSP 发布、录像和快照。
整个链路可以概括为:
这种“外部采集 + SDK 投递”模式非常适合 macOS。
因为桌面端的采集方式更加多样:
- 可以采集整个显示器;
- 可以只采集某个窗口;
- 可以采集内置或外接摄像头;
- 可以使用 Continuity Camera;
- 可以采集麦克风;
- 可以采集系统播放声音;
- 可以在运行中调整窗口大小;
- 可以根据业务切换音频来源。
让上层掌握采集源,能够保留桌面应用所需要的灵活性;SDK 则专注于编码和协议链路,避免每个项目重复实现复杂的音视频基础设施。
三、摄像头采集:尽量直接输出编码器友好的NV12
SmartMacPublisher 的摄像头采集基于 AVFoundation。
Demo 会先检查摄像头和麦克风权限,然后创建 AVCaptureSession,添加视频输入、音频输入以及对应的数据输出。
在视频输出配置中,Demo 主动要求 AVFoundation 输出 NV12:
AVCaptureVideoDataOutput *videoOutput =
[[AVCaptureVideoDataOutput alloc] init];
videoOutput.videoSettings = @{
(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:
@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
};
videoOutput.alwaysDiscardsLateVideoFrames = YES;
[videoOutput setSampleBufferDelegate:self
queue:self.captureQueue];
这里有两个值得关注的细节。
1. 摄像头直接输出NV12
NV12 是 VideoToolbox 编码链路中非常常见的输入格式。
摄像头直接输出 NV12,可以避免先得到 BGRA,再通过 CPU 做一次颜色空间转换,从而减少:
- CPU 占用;
- 内存带宽消耗;
- 视频帧拷贝;
- 格式转换引入的排队延迟。
对于摄像头推流来说,如果不需要复杂美颜、叠加和图像处理,尽量保持 CVPixelBuffer → VideoToolbox 的短链路,是降低端侧延迟和功耗的重要方式。
2. 丢弃处理不及时的旧帧
videoOutput.alwaysDiscardsLateVideoFrames = YES;
实时推流和离线视频处理的目标不同。
离线任务更关注“每一帧都不能丢”,而实时直播更关注“当前看到的是最新画面”。如果应用来不及处理某些帧,继续缓存旧帧只会导致延迟越来越大。
因此,在实时采集场景下,及时丢弃已经过期的帧,通常比积压帧队列更合理。
四、摄像头分辨率与帧率:不能只依赖SessionPreset
macOS 摄像头的设备类型比较复杂,可能包括:
- MacBook 内置摄像头;
- USB 摄像头;
- 采集卡;
- 虚拟摄像头;
- Continuity Camera;
- 第三方 DAL 驱动设备。
不同设备支持的分辨率和帧率组合并不完全一致。
SmartMacPublisher 会枚举 AVCaptureDeviceFormat,按照宽高对 Format 进行归类,再根据目标帧率选择合适的 Format。
AVCaptureDeviceFormat *targetFormat =
[selectedResolution bestFormatForFPS:selectedFPS];
if ([videoDevice lockForConfiguration:&error]) {
videoDevice.activeFormat = targetFormat;
[videoDevice unlockForConfiguration];
}
Demo 没有完全依赖 sessionPreset,还会在 AVCaptureVideoDataOutput 中明确设置目标宽高:
videoOutput.videoSettings = @{
(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:
@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange),
(__bridge NSString *)kCVPixelBufferWidthKey:
@(selectedResolution.width),
(__bridge NSString *)kCVPixelBufferHeightKey:
@(selectedResolution.height)
};
这是一个很实际的工程处理。
部分 Continuity Camera 或外接设备即使已经设置了 activeFormat,仍可能按照硬件原生分辨率输出。明确指定输出宽高,可以让采集管线对输出帧进行统一约束,避免:
- UI 中选择的是 1280×720;
- 实际送给编码器的却是 1920×1080;
- SDK 外部分辨率配置与真实帧尺寸不一致。
五、屏幕和窗口采集:基于ScreenCaptureKit实现
SmartMacPublisher 的屏幕和窗口采集基于 ScreenCaptureKit。
代码中针对不同 macOS 版本采用了不同处理方式:
- macOS 12.3 及以上:支持 ScreenCaptureKit;
- macOS 13.0 及以上:支持通过 SCStream 采集系统声音;
- macOS 14.0 及以上:优先使用系统
SCContentSharingPicker; - macOS 14.0 及以上:支持监听窗口内容尺寸变化并动态更新采集配置。
在 macOS 14 及以上版本,Demo 直接调用系统内容选择器:
SCContentSharingPickerConfiguration *config =
[[SCContentSharingPickerConfiguration alloc] init];
config.allowedPickerModes =
SCContentSharingPickerModeSingleDisplay |
SCContentSharingPickerModeSingleWindow;
SCContentSharingPicker *picker =
[SCContentSharingPicker sharedPicker];
picker.defaultConfiguration = config;
picker.active = YES;
[picker present];
用户可以使用系统界面选择整个显示器或某个具体窗口。
相较于自行枚举窗口,系统选择器在权限、安全性和用户体验方面更符合 macOS 的设计逻辑,也可以减少重复触发授权流程带来的困扰。
对于 macOS 12.3 至 13.x,Demo 则通过 SCShareableContent 获取可共享的显示器和窗口,再使用自定义窗口完成选择。
六、为什么屏幕采集需要BGRA转NV12?
ScreenCaptureKit 在当前 Demo 中配置为输出 BGRA:
SCStreamConfiguration *config =
[[SCStreamConfiguration alloc] init];
config.pixelFormat = kCVPixelFormatType_32BGRA;
config.showsCursor = self.showCursor;
config.minimumFrameInterval =
CMTimeMake(1, MAX(1, self.fps));
之所以不直接要求 ScreenCaptureKit 输出 NV12,是因为在部分较早的 macOS 12.x 版本中,NV12 并不是所有采集路径都能稳定支持的输出格式,可能导致 SCStream 启动失败。
因此,SmartMacPublisher 选择了一条兼容性更好的路径:
- ScreenCaptureKit 稳定输出 BGRA;
- 应用层使用
VTPixelTransferSession转换为 NV12; - 将转换后的
CMSampleBufferRef投递给 SDK; - SDK 再交给 VideoToolbox 编码。
核心代码如下:
if (!_pixelTransferSession) {
VTPixelTransferSessionCreate(
kCFAllocatorDefault,
&_pixelTransferSession);
}
CVPixelBufferRef dstPixelBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(
kCFAllocatorDefault,
_nv12Pool,
&dstPixelBuffer);
VTPixelTransferSessionTransferImage(
_pixelTransferSession,
srcPixelBuffer,
dstPixelBuffer);
这里没有使用 CPU 逐像素转换,而是使用 VideoToolbox 提供的 Pixel Transfer 能力。
同时,Demo 根据采集分辨率创建并复用 CVPixelBufferPool,只有尺寸发生变化时才重建 Buffer Pool,从而降低频繁分配内存带来的开销。
转换完成后,Demo 会保留原始视频帧的时间信息,重新构造 CMSampleBufferRef:
CMSampleTimingInfo timing = kCMTimingInfoInvalid;
CMSampleBufferGetSampleTimingInfo(
sourceSampleBuffer,
0,
&timing);
CMSampleBufferCreateForImageBuffer(
kCFAllocatorDefault,
dstPixelBuffer,
YES,
NULL,
NULL,
formatDescription,
&timing,
&result);
这保证了视频格式发生变化,但时间戳语义仍然保持一致。
七、窗口尺寸变化:固定分辨率还是动态跟随?
窗口采集有一个摄像头采集通常不会遇到的问题:
被采集窗口可能在推流过程中被用户拖动和缩放。
SmartMacPublisher 提供了两种策略。
固定推流分辨率
窗口尺寸发生变化时,仍然保持原来的编码分辨率,对窗口内容进行居中或缩放适配。
这种方式的优点是:
- 编码器参数稳定;
- 服务端和播放端不需要处理分辨率变化;
- 更适合持续直播和多终端观看;
- 不容易引发播放器重新建解码器。
跟随窗口分辨率
在 macOS 14 及以上版本,Demo 可以监听 SCStreamConfiguration 更新,并调用:
[stream updateConfiguration:newConfiguration
completionHandler:^(NSError *error) {
// 更新成功
}];
为了避免用户连续拖动窗口时频繁更新,代码增加了约 500ms 的防抖逻辑。
当新的分辨率真正生效后,Demo 还会通知 SDK:
[self.publisherSDK
SmartPublisherSetExternalResolution:newWidth
height:newHeight];
动态跟随更适合:
- 开发调试工具;
- 单窗口远程预览;
- 分辨率变化必须完整保留的工业软件;
- 接收端能够正确处理编码参数变化的私有链路。
而在面向公网直播或大量播放端时,通常更建议保持固定编码分辨率。
八、第一帧到达后再启动推流:一个容易忽略的关键细节
SmartMacPublisher 没有在用户点击按钮后立刻调用 RTMP、RTSP 或录像启动接口。
它首先判断是否已经收到有效视频帧:
if (self.lastVideoWidth <= 0 ||
self.lastVideoHeight <= 0) {
self.pendingStartPublishing = YES;
[self appendLog:
@"等待第一帧视频到达后再启动 RTMP 推流…"];
return;
}
当第一帧到达时,Demo 从 CVPixelBuffer 中读取实际宽高:
NSInteger width =
CVPixelBufferGetWidth(pixelBuffer);
NSInteger height =
CVPixelBufferGetHeight(pixelBuffer);
self.lastVideoWidth = width;
self.lastVideoHeight = height;
[self.publisherSDK
SmartPublisherSetExternalResolution:width
height:height];
然后统一处理之前挂起的操作:
- (void)_flushPendingStarts {
BOOL startRTMP = self.pendingStartPublishing;
BOOL startRecord = self.pendingStartRecording;
BOOL startRTSP = self.pendingStartRtspStream;
self.pendingStartPublishing = NO;
self.pendingStartRecording = NO;
self.pendingStartRtspStream = NO;
if (startRTMP) [self startPublishing];
if (startRecord) [self startRecording];
if (startRTSP) [self startRtspStream];
}
这看似只是一个启动顺序问题,实际上会直接影响首屏和编码稳定性。
如果视频宽高尚未确定就启动编码器,可能出现:
- 编码器初始化参数不完整;
- 第一段视频数据尺寸错误;
- 推流已连接但长时间没有有效视频;
- 录像文件头信息与实际视频不一致;
- 屏幕采集尺寸变化后参数未同步。
因此,“先采集、等首帧、确认分辨率、再启动输出”,是外部视频数据投递模式中非常重要的工程策略。
九、SDK初始化:外部CMSampleBuffer投递模式
SmartMacPublisher 初始化 SmartPublisherSDK 时,将音频和视频模式都设置为 3:
static const NSInteger kExternalAudio = 3;
static const NSInteger kExternalVideo = 3;
[self.publisherSDK SmartPublisherInit:kExternalAudio
video_opt:kExternalVideo];
这里的含义是:
- 音频由上层采集,以编码前
CMSampleBufferRef投递; - 视频由上层采集,以编码前
CMSampleBufferRef投递; - SDK 不负责选择具体摄像头、屏幕或窗口;
- SDK 负责后续编码、封装和协议输出。
初始化完成后,Demo 配置编码参数:
[self.publisherSDK
SmartPublisherSetVideoEncoderType:
NT_MEDIA_CODEC_ID_H264
isHwEncoder:YES];
[self.publisherSDK
SmartPublisherSetAudioEncoderType:1
isHwEncoder:NO];
[self.publisherSDK SmartPublisherSetFPS:fps];
[self.publisherSDK SmartPublisherSetGopInterval:gop];
[self.publisherSDK
SmartPublisherSetVideoBitRate:bitrate
maxBitRate:bitrate];
当前 SmartMacPublisher Demo 默认采用:
- H.264 视频编码;
- VideoToolbox 硬件编码;
- AAC 音频编码;
- 可配置 FPS、GOP 和视频码率。
SmartPublisherSDK 的视频编码接口也支持选择 H.265,但在实际 RTMP 推流项目中,是否采用 H.265,还需要结合服务器、封装格式和播放端兼容性综合判断。
对于通用 RTMP 直播链路,H.264 仍然是兼容性更稳妥的选择;对于私有 RTSP、局域网或明确支持 HEVC 的系统,则可以评估 H.265。
十、RTMP推流:面向云端、中心服务器和业务平台
RTMP 仍然是直播采集端最常见的上行协议之一。
它的优势主要体现在:
- 流媒体服务器支持广泛;
- CDN 和私有直播平台接入成熟;
- 推流端实现和部署相对简单;
- 适合从边缘采集端向中心服务器汇聚;
- 便于后续转码、录制、分发和内容审核。
SmartMacPublisher 启动 RTMP 推流的代码很简洁:
NSString *url =
self.urlTextField.stringValue;
NSInteger ret =
[self.publisherSDK
SmartPublisherStartPublisher:url];
if (ret == 0) {
self.isRtmpPublishing = YES;
}
真实项目中,上层可以将地址替换为:
rtmp://server-address/live/stream-name
摄像头模式适合:
- 企业活动直播;
- 在线面试;
- 会议转播;
- 客服讲解;
- 远程医疗咨询;
- 主播工具。
屏幕或窗口模式则适合:
- PPT 课件直播;
- 软件操作演示;
- 编程和技术培训;
- 工业控制界面共享;
- 远程售后支持;
- 金融行情和数据分析展示;
- 产品发布会和导播信号接入。
需要说明的是,低延迟不是单个推流接口决定的。
最终端到端延迟还会受到以下因素影响:
- 采集帧率;
- 视频编码参数;
- GOP 长度;
- 上行网络;
- RTMP 服务器缓存;
- 转码和分发链路;
- 播放器缓冲策略。
SmartMacPublisher 的目标,是尽可能压缩采集端和编码端的排队与转换开销,为后续低延迟链路提供基础。
十一、轻量级RTSP服务:让Mac直接成为局域网视频源
RTMP 更适合将流推送到云端或中心服务器,而 RTSP 更适合:
- 安防监控;
- 工业设备;
- 摄像机和 NVR;
- 局域网预览;
- 实验室联调;
- 边缘侧视频分发。
SmartMacPublisher 内置了轻量级 RTSP 服务。
Demo 默认使用:
static const NSInteger kRtspPort = 8554;
static NSString * const kRtspStreamName = @"stream1";
启动 RTSP 服务分为三个步骤:
self.rtspServerSDK =
[[SmartRTSPServerSDK alloc] init];
self.rtspServerHandle =
[self.rtspServerSDK OpenRtspServer:0];
[self.rtspServerSDK
SetRtspServerPort:self.rtspServerHandle
port:8554];
[self.rtspServerSDK
StartRtspServer:self.rtspServerHandle
reserve:0];
随后把当前 SmartPublisherSDK 产生的媒体流绑定到该 RTSP Server:
[self.publisherSDK SetRtspStreamName:@"stream1"];
[self.publisherSDK ClearRtspStreamServer];
[self.publisherSDK
AddRtspStreamServer:self.rtspServerHandle
reserve:0];
[self.publisherSDK StartRtspStream:0];
局域网内的客户端即可通过类似地址播放:
rtsp://Mac的局域网IP:8554/stream1
例如:
rtsp://192.168.1.100:8554/stream1
这种模式不需要单独部署流媒体服务器,Mac 本身就可以成为一个临时 RTSP 视频源。
十二、轻量级RTSP服务适合哪些场景?
1. 局域网临时视频分发
在展会、实验室或设备联调现场,可以直接把 Mac 的摄像头或屏幕发布成 RTSP 流。
其他电脑、移动终端或测试设备只需要知道 RTSP 地址,就可以直接拉流。
2. 工业现场预览
工业软件、检测系统和控制界面经常运行在桌面端。
将指定窗口采集后通过 RTSP 发布,可以让局域网内的监控终端或远程操作站实时查看。
3. 安防系统临时接入
部分安防平台习惯以 RTSP 地址接入视频源。
通过轻量级 RTSP 服务,可以把 Mac 屏幕、USB 摄像头或采集卡包装成标准 RTSP 视频源,降低现有系统的接入成本。
4. 播放器和服务端测试
开发低延迟播放器、录像系统、转发网关或视频分析程序时,需要一个可控的视频源。
相比依赖外部网络摄像机,使用 Mac 直接生成 RTSP 流,更容易:
- 切换不同分辨率;
- 调整码率和帧率;
- 产生屏幕动态内容;
- 复现特定问题;
- 对比不同播放器的延迟和稳定性。
5. 无需中心服务器的小型系统
对于只在局域网使用的小型项目,单独部署大型流媒体服务器可能并不划算。
轻量级 RTSP 服务可以直接嵌入业务程序,减少安装、配置和运维成本。
十三、RTMP与RTSP不是替代关系,而是互补关系
RTMP 和轻量级 RTSP 服务面向的链路并不相同。
| 对比维度 | RTMP推流 | 轻量级RTSP服务 |
|---|---|---|
| 典型网络 | 公网、专网、中心化网络 | 局域网、设备网、边缘网络 |
| 主要方向 | Mac主动推向服务器 | 客户端主动从Mac拉流 |
| 典型场景 | 直播平台、云端汇聚、CDN | 安防、工业、本地预览、设备联调 |
| 是否需要外部服务器 | 通常需要 | 不需要 |
| 部署复杂度 | 需要准备RTMP服务地址 | 应用内启动即可 |
| 链路特点 | 适合中心化分发 | 链路短、依赖少 |
SmartMacPublisher 同时支持两种输出方式,意味着一份采集数据可以服务多条业务链路:
摄像头 / 屏幕 / 窗口
│
▼
SmartPublisherSDK
│
├── RTMP推送到中心服务器
├── 发布到本地RTSP服务
├── 本地MP4录像
└── 实时快照
例如,现场画面可以一路通过 RTMP 推送给远程平台,同时通过 RTSP 提供给局域网监视器,并在 Mac 本地完成录像留档。
十四、音频采集:麦克风与系统声音如何统一处理?
macOS 屏幕推流如果只有视频,往往无法满足真实业务需求。
常见音频需求包括:
- 老师讲课时采集麦克风;
- 播放课件视频时采集系统声音;
- 软件演示时采集讲解声音;
- 会议转播时采集会议音频;
- 游戏和多媒体内容采集应用声音。
SmartMacPublisher 支持:
- AVFoundation 麦克风采集;
- ScreenCaptureKit 系统声音采集;
- 麦克风和系统声音之间的实时选择。
麦克风音频格式
macOS 麦克风默认可能输出 Float32、Non-Interleaved 的 PCM 数据,而 SDK 的 AAC 编码链路需要规范的 PCM 输入。
因此 Demo 在 AVCaptureAudioDataOutput 中明确要求:
audioOutput.audioSettings = @{
AVFormatIDKey:
@(kAudioFormatLinearPCM),
AVNumberOfChannelsKey:
@1,
AVLinearPCMBitDepthKey:
@16,
AVLinearPCMIsFloatKey:
@NO,
AVLinearPCMIsBigEndianKey:
@NO,
AVLinearPCMIsNonInterleaved:
@NO
};
也就是:
- 16-bit;
- Signed Integer;
- 单声道;
- Interleaved PCM。
系统声音格式转换
ScreenCaptureKit 输出的系统声音通常是 Float32 PCM。
Demo 使用 Accelerate 框架中的 vDSP 将 Float32 转换为 Int16:
float scale = 32767.0f;
vDSP_vsmul(floatSamples,
1,
&scale,
tempSamples,
1,
sampleCount);
vDSP_vclip(tempSamples,
1,
&low,
&high,
tempSamples,
1,
sampleCount);
vDSP_vfixr16(tempSamples,
1,
int16Samples,
1,
sampleCount);
与逐样本手工循环相比,vDSP 更适合持续实时音频处理。
推流中切换音频来源
在屏幕采集模式下,Demo 可以同时保持麦克风和系统声音采集,再根据用户当前选择决定投递哪一路:
if (self.audioSourcePreferMic) {
// 投递麦克风,忽略系统声音
} else {
// 投递系统声音,忽略麦克风
}
这样切换音频源时,不必重新创建整个视频编码和推流链路。
十五、录像与快照:不仅要实时传输,还要形成业务闭环
真实项目中的推流工具通常不能只解决“把视频发出去”这一件事。
很多场景还需要本地留档:
- 培训课程录像;
- 设备异常过程记录;
- 工业巡检取证;
- 软件操作回放;
- 直播问题复现;
- 监控画面抓拍;
- 远程协助操作记录。
SmartMacPublisher 支持本地 MP4 录像。
SDK 初始化时设置录像目录:
[self.publisherSDK
SmartPublisherSetRecorderDirectory:recordDirectory];
[self.publisherSDK
SmartPublisherSetRecorderFileMaxSize:200];
[self.publisherSDK SmartPublisherSetRecorderAudio:1];
[self.publisherSDK SmartPublisherSetRecorderVideo:1];
Demo 默认将录像保存到:
~/Movies/SmartMacPublisher
单个录像文件最大设置为 200MB,达到上限后可自动切换到新文件。
启动录像:
[self.publisherSDK SmartPublisherStartRecorder];
停止录像:
[self.publisherSDK SmartPublisherStopRecorder];
快照保存目录为:
~/Pictures/SmartMacPublisher
保存当前帧:
[self.publisherSDK SmartPublisherSaveCurImage:imagePath];
这些能力可以与 RTMP 和 RTSP 组合使用:
- 只录像;
- 只 RTMP 推流;
- 只发布 RTSP;
- RTMP 推流同时录像;
- RTSP 发布同时录像;
- RTMP、RTSP、录像同时运行。
这种多输出组合,在工业、教育、安防和企业直播项目中非常实用。
十六、事件回调:上层必须知道链路正在发生什么
推流不是一次普通的网络请求,而是一个持续运行的状态机。
上层应用必须知道:
- 是否开始连接;
- 是否连接成功;
- 是否连接失败;
- 是否发生断线;
- SDK 是否准备重连;
- 当前发送是否出现延迟;
- 是否创建了新录像文件;
- 一个录像文件是否已经写入完成;
- 快照是否保存成功;
- RTSP 地址是否生成;
- RTSP 服务是否返回异常状态。
SmartMacPublisher 通过 SmartPublisherDelegate 接收事件。
例如:
case EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
status = @"RTMP推流中(已连接)";
break;
case EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
case EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
status = @"RTMP推流中(重连中)";
break;
case EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
log = [NSString stringWithFormat:
@"发送延迟:%llu毫秒", param1];
break;
录像和快照也会产生独立事件:
新录制文件创建
单个录像文件完成
快照保存成功
RTSP地址生成
RTSP服务器状态码
没有事件回调时,应用通常只能显示一个模糊的“推流中”。
有了完整事件,上层才能真正实现:
- 状态栏;
- 日志面板;
- 失败提示;
- 自动恢复提示;
- 运行健康监控;
- 业务统计和异常上报。
十七、RTSP会话数查询:服务启动不等于真的有人观看
轻量级 RTSP 服务启动成功,只能说明端口已经开始监听,并不能说明已经有客户端连接。
SmartMacPublisher 提供了会话数查询:
NSInteger sessionCount = 0;
[self.rtspServerSDK
GetRtspServerClientSessionNumbers:
self.rtspServerHandle
session_numbers:&sessionCount];
这一能力可以用于:
- UI 显示当前观看人数;
- 判断是否存在活跃客户端;
- 无客户端时降低采集频率;
- 无人观看时延迟启动某些业务;
- 设备运行状态监控;
- 限制最大客户端数量。
对于嵌入式 RTSP 服务而言,“可查询会话状态”比单纯提供一个启动接口更有工程价值。
十八、资源释放与状态组合:真正容易出问题的地方
音视频应用中,启动接口通常不难,难的是正确停止。
SmartMacPublisher 明确限制:
RTMP 推流、RTSP 流和录像没有停止之前,不能直接停止采集。
代码中会先检查:
if (self.isRtmpPublishing ||
self.isRecording ||
self.isRtspPublishing) {
[self appendLog:
@"请先停止推流、RTSP流和录制,再停止采集。"];
return;
}
SDK 也只有在所有输出都停止后才会反初始化:
- (void)tryUninitSDK {
if (self.isRtmpPublishing ||
self.isRecording ||
self.isRtspPublishing ||
!self.sdkInitialized) {
return;
}
self.publisherSDK.delegate = nil;
[self.publisherSDK SmartPublisherUnInit];
self.publisherSDK = nil;
self.sdkInitialized = NO;
}
这样的生命周期设计可以避免:
- 录像仍在写文件时释放 SDK;
- RTSP 客户端仍在拉流时关闭编码器;
- 视频回调仍在执行时销毁对象;
- 摄像头指示灯无法关闭;
- CoreAudio 回调未退出;
- 应用关闭时发生野指针访问。
在停止采集时,Demo 还会在对应串行队列中同步等待 stopRunning 完成,确保回调真正结束后再释放相关对象。
这些代码不一定是 Demo 中最“显眼”的部分,却往往决定了产品是否能够长期稳定运行。
十九、典型应用场景
1. macOS同屏直播
老师、培训师和工程师可以将整个屏幕或指定窗口推送到 RTMP 服务器。
适用于:
- 在线教学;
- 技术培训;
- 软件演示;
- 产品发布;
- 金融投研;
- 设计协作;
- 远程售后支持。
相比浏览器屏幕共享,SDK 方案更适合需要:
- 私有化部署;
- 自定义 UI;
- 自定义推流协议;
- 本地录像;
- 低延迟播放;
- 与现有业务系统深度集成。
2. 摄像头直播采集端
使用 Mac 内置摄像头、USB 摄像头、采集卡或者 Continuity Camera,可以快速构建 macOS 直播采集工具。
适用于:
- 企业直播;
- 会议转播;
- 在线面试;
- 客服讲解;
- 医疗咨询;
- 远程培训。
3. 工业视觉与远程巡检
工业软件、检测界面和设备控制程序通常运行在桌面系统中。
通过窗口采集,可以只传输目标软件画面,而不暴露整个桌面。
视频既可以通过 RTMP 推送到远程调度中心,也可以通过 RTSP 提供给局域网监控终端。
4. 局域网轻量视频分发
一台 Mac 可以临时充当视频源,局域网内多个终端直接通过 RTSP 地址观看。
适用于:
- 展会演示;
- 实验室测试;
- 设备联调;
- 会议室视频分发;
- 安防系统临时接入。
5. 流媒体开发与测试
SmartMacPublisher 可以快速产生可控的 RTMP 或 RTSP 测试流,用于验证:
- 低延迟播放器;
- RTMP服务器;
- RTSP客户端;
- 转码服务;
- 录像服务;
- 视频分析程序;
- 弱网重连策略;
- 动态分辨率兼容性。
相比使用固定网络摄像机,Mac 屏幕和摄像头更容易制造不同的动态画面和测试条件。
二十、SmartMediaKit macOS方案的技术优势
结合 SmartMacPublisher 的实现,这套方案的优势主要体现在以下几个方面。
1. 采集源覆盖完整
支持摄像头、麦克风、屏幕、窗口和系统声音,覆盖 macOS 常见实时采集需求。
2. 上层采集模型开放
应用层通过 AVFoundation 和 ScreenCaptureKit 自主控制采集,SDK 不限制业务如何选择和组合采集源。
3. CMSampleBuffer接口边界清晰
上层只需要投递编码前音视频数据,编码、封装、推流、RTSP、录像和事件处理由 SDK 完成。
4. 充分利用macOS硬件媒体能力
摄像头 NV12 直通、VTPixelTransferSession 格式转换和 VideoToolbox 硬件编码,有利于降低 CPU 占用与端侧延迟。
5. 同时覆盖公网和局域网链路
RTMP 适合云端与中心服务器,轻量级 RTSP 服务适合局域网和边缘侧实时分发。
6. 输出能力可以组合
RTMP、RTSP、录像和快照可以围绕同一份采集数据协同运行。
7. 工程细节相对完整
Demo 不只是简单调用几个接口,还处理了:
- 权限申请;
- 系统内容选择器;
- 摄像头设备枚举;
- 分辨率与帧率匹配;
- 首帧等待;
- 动态分辨率;
- BGRA 转 NV12;
- Float32 音频转 Int16;
- 音频源切换;
- RTSP 会话查询;
- 事件状态更新;
- SDK 生命周期;
- 安全停止和资源释放。
这些内容正是从 Demo 走向真实产品时最容易踩坑的部分。
二十一、为什么SmartMediaKit要补齐macOS推流能力?
从产品战略看,macOS 推流并不是简单的平台补齐,而是 SmartMediaKit 实时音视频链路向桌面端采集与边缘分发的一次延伸。
过去的跨平台直播 SDK,往往将 Mac 定位为播放器、监控端或调试端。
但现在,Mac 越来越多地参与实时内容生产:
- 作为课件和软件画面的采集端;
- 作为摄像头直播终端;
- 作为远程技术支持工具;
- 作为工业现场的视频采集节点;
- 作为局域网 RTSP 视频源;
- 作为直播和流媒体系统的测试信号发生器。
因此,一个完整的 macOS 实时音视频方案,不能只有“播放”,还需要同时具备:
采集 + 编码 + 推流 + 本地分发 + 录像 + 快照 + 状态管理
SmartMacPublisher 展示的正是这样一条完整链路。
二十二、总结
大牛直播SDK(SmartMediaKit)推出 macOS 版低延迟 RTMP 推流和轻量级 RTSP 服务,并不是简单地把其他平台接口移植到 Mac,而是结合 macOS 桌面采集特点,重新设计了一套更加开放的实时音视频架构。
在这套架构中:
- AVFoundation 负责摄像头和麦克风采集;
- ScreenCaptureKit 负责屏幕、窗口和系统声音采集;
- 摄像头尽量直接输出 NV12;
- 屏幕 BGRA 通过 VTPixelTransferSession 转为 NV12;
- 系统声音通过 Accelerate/vDSP 转为 Int16 PCM;
- 音视频以
CMSampleBufferRef投递给 SmartPublisherSDK; - SDK 负责 VideoToolbox 视频编码、AAC 音频编码、RTMP 推流、轻量级 RTSP 发布、MP4 录像、快照和事件回调。
SmartMacPublisher 还重点处理了第一帧等待、真实分辨率获取、窗口尺寸变化、音频源切换、多输出状态组合以及安全资源释放等实际工程问题。
对于正在开发以下产品的团队:
- macOS 同屏直播;
- 摄像头直播采集端;
- 在线教学和远程培训;
- 工业视觉远程预览;
- 局域网 RTSP 分发;
- 企业私有化直播;
- 流媒体开发测试工具;
- 边推边录与视频留档系统;
SmartMediaKit macOS 推流方案可以减少重复开发底层音视频模块的成本,让团队更快完成从采集、编码到分发和留档的完整业务闭环。
从更长远的角度看,macOS 已经不再只是实时视频的观看终端,也正在成为桌面端内容生产、边缘采集和本地视频分发的重要节点。