首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >iOS如何实现RTSP/RTMP低延迟播放?SmartMediaKit播放器集成实践

iOS如何实现RTSP/RTMP低延迟播放?SmartMediaKit播放器集成实践

原创
作者头像
音视频牛哥
发布2026-06-13 16:34:10
发布2026-06-13 16:34:10
210
举报

​ 摘要: 在安防监控、无人机图传、工业巡检、远程运维、移动导播等场景中,iOS端播放器并不只是“把视频显示出来”这么简单。一个可实际落地的RTSP/RTMP直播播放模块,需要同时兼顾低延迟、首屏速度、软硬解兼容、横竖屏适配、录像快照、弱网状态监控、YUV/SEI回调以及播放器生命周期管理。本文结合大牛直播SDK(SmartMediaKit)iOS版SmartiOSPlayerV2 Demo,系统梳理RTSP/RTMP直播播放器在iPhone/iPad端的集成方式和关键设计点。

关键词:SmartMediaKit、iOS播放器、RTSP播放器、RTMP播放器、低延迟直播、VideoToolbox、Metal、AVSampleBufferDisplayLayer、音视频开发


移动端直播播放器,不只是播放一个URL

在iOS端做点播播放器,开发者更多关注的是进度条、倍速、缓存、字幕和本地文件兼容;但直播播放器面对的是持续到达、不可预知、不可回退的实时码流。

尤其在RTSP/RTMP直播场景中,播放器需要处理的问题远比“拉流、解码、显示”复杂:

  • RTSP/RTMP协议接入;
  • RTSP TCP/UDP传输策略;
  • 首屏时间和端到端延迟控制;
  • H.264/H.265软解和VideoToolbox硬解;
  • 网络抖动、丢包、断线和无数据检测;
  • Metal渲染、Layer直显和横竖屏布局;
  • 音量、静音、旋转、镜像、比例绘制;
  • 本地录像、快照和录像文件回放;
  • 下载速度、丢包率、分辨率等状态回调;
  • YUV原始帧回调和SEI用户数据回调;
  • 页面退出、播放器停止、View释放和SDK反初始化。

很多播放器Demo只演示“开始播放”和“停止播放”,但真正进入项目后,稳定性往往不是毁在主流程,而是毁在异常流程:比如停止播放失败后提前释放View、旧实例事件覆盖新实例状态、横竖屏切换后渲染层尺寸异常、录像和播放共用实例时释放时机不对等。

SmartMediaKit iOS版SmartiOSPlayerV2 Demo的价值,正是在于它不是一个极简播放样例,而是把播放器集成中常见的工程问题也一并考虑进去了。


Demo整体能力概览

SmartiOSPlayerV2基于Objective-C和UIKit实现。竖屏状态下,上方为直播画面,下方为可滚动控制区;横屏状态下自动进入全屏播放,并提供返回竖屏按钮。

Demo覆盖的核心能力如下:

能力

说明

RTSP/RTMP播放

支持输入RTSP或RTMP直播地址进行播放

RTSP传输模式

支持TCP/UDP选择,并可配置自动切换

低延迟播放

支持低延迟模式、快速启动和播放缓冲配置

三种解码模式

软解、普通硬解、硬解Layer直显

视频渲染

普通模式使用Metal渲染,适配iPhone和iPad

横竖屏适配

竖屏控制面板、横屏全屏播放、主动返回竖屏

比例绘制

支持保持比例或拉伸铺满

播放中切换URL

支持主备流、轮巡流或测试流动态切换

音频控制

支持音量调节、静音和取消静音

图像调节

支持亮度、对比度、饱和度和一键重置

画面变换

支持90度旋转、水平镜像、垂直镜像

本地录像

支持MP4录像、文件大小限制和音频转AAC

录像回放

独立RecorderView页面支持本地录像文件播放

快照

支持保存当前画面为PNG并写入系统相册

YUV回调

可选I420 YUV回调,便于AI分析或自定义处理

SEI用户数据

支持用户数据回调,可用于业务信息透传

状态监控

支持连接状态、分辨率、下载速度、丢包率等事件

从功能覆盖看,这个Demo已经接近一个移动端RTSP/RTMP播放测试工具的完整形态,开发者可以在此基础上改造成监控预览、无人机图传预览、工业巡检终端、移动导播预览或内部流媒体测试工具。


典型使用场景

移动安防监控

在安防项目中,iPhone或iPad常用于查看IPC、NVR或边缘设备输出的RTSP视频。播放器需要支持横屏查看、低延迟预览、快照留证、本地录像和异常状态提示。

SmartMediaKit播放器模块可以直接用于移动监控客户端,也可以作为项目中的视频预览组件集成到现有App中。

无人机、机器人和远程操控

无人机图传、机器人巡检和远程操控对延迟非常敏感。播放侧需要尽可能减少缓冲等待,同时保持解码和渲染链路稳定。

Demo默认开启低延迟模式、快速启动,并将播放缓冲设置为0,适合局域网或专网环境下追求实时性的预览场景。对于公网或弱网环境,也可以切换RTSP TCP模式,提高连续性。

工业巡检和远程运维

工业现场的视频流可能来自摄像机、采集盒、边缘网关或自定义推流设备。移动端通常承担实时查看、异常留档和远程辅助判断的角色。

Demo提供的录像、快照、SEI用户数据回调和下载速度回调,适合和设备状态、AI检测结果、巡检事件等业务数据结合。

教育直播和移动导播

iPad可以作为移动预览终端,用于查看不同直播源、切换主备流、确认输入信号是否正常。播放中动态切换URL和下载速度回调,对导播预览、流媒体测试和直播前检查非常实用。


播放器状态设计:播放、录像和SDK实例分离

直播播放器生命周期是最容易被忽视的部分。

SmartiOSPlayerV2没有简单地用一个isPlaying控制全部状态,而是分别维护:

代码语言:javascript
复制
BOOL is_inited_player_;   // SDK是否已初始化
BOOL is_playing_;         // 是否正在播放
BOOL is_recording_;       // 是否正在录像

这样设计的原因是,播放和录像并不是完全绑定的:

  • 可以只播放,不录像;
  • 可以播放中启动录像;
  • 可以停止播放但继续录像;
  • 录像和播放可以共用同一个SDK实例;
  • 只有播放和录像都停止后,才能安全释放SDK。

整体调用关系可以理解为:

代码语言:javascript
复制
InitPlayer
    ├── StartPlayer
    └── StartRecorder

StopPlayer / StopRecorder
    └── TryUnInitPlayer
            └── 播放和录像均停止后,再执行UnInitPlayer

页面销毁时,Demo也遵循更安全的释放顺序:

代码语言:javascript
复制
停止录像 -> 停止播放 -> 释放播放View -> UnInit SDK

这类细节在Demo阶段看起来“多写了一点代码”,但在实际项目中非常关键。特别是播放View由SDK创建并由底层渲染线程使用,如果SmartPlayerStop失败,不能简单地马上移除和释放UIView,否则可能出现渲染线程仍访问已释放对象的风险。


初始化播放器和低延迟参数配置

播放器初始化主要在InitPlayer中完成:

代码语言:javascript
复制
_smart_player_sdk = [[SmartPlayerSDK alloc] init];
_smart_player_sdk.delegate = self;

NSInteger ret = [_smart_player_sdk SmartPlayerInitPlayer];
if (ret != DANIULIVE_RETURN_OK) {
    return NO;
}

随后设置播放地址:

代码语言:javascript
复制
NSString *initialURL = [self currentPlaybackURL];

[_smart_player_sdk SmartPlayerSetPlayURL:initialURL];

playback_url_ = [initialURL copy];
self.playbackURLTextField.text = playback_url_;

Demo默认播放地址为RTSP地址:

代码语言:javascript
复制
playback_url_ = @"rtsp://192.168.0.103:18554/stream1";

也可以切换为RTMP地址:

代码语言:javascript
复制
// playback_url_ = @"rtmp://192.168.0.102:1935/hls/stream1";

低延迟相关配置如下:

代码语言:javascript
复制
is_fast_startup_ = YES;         // 快速启动
is_low_latency_mode_ = YES;     // 低延迟模式
buffer_time_ = 0;               // 0ms播放缓冲
is_rtsp_tcp_mode_ = NO;         // 默认RTSP UDP模式

对应SDK接口:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetLowLatencyMode:is_low_latency_mode_];
[_smart_player_sdk SmartPlayerSetBuffer:buffer_time_];
[_smart_player_sdk SmartPlayerSetFastStartup:is_fast_startup_];
[_smart_player_sdk SmartPlayerSetRTSPTcpMode:is_rtsp_tcp_mode_];
[_smart_player_sdk SmartPlayerSetRTSPTimeout:10];
[_smart_player_sdk SmartPlayerSetRTSPAutoSwitchTcpUdp:1];

这组配置偏向实时性,适合局域网、专网、图传和远程操控类场景。

但在实际项目中,低延迟配置不能孤立看待。端到端延迟还会受到采集端缓存、编码器GOP、B帧、服务端转发、网络排队、播放器缓冲和解码等待关键帧等因素影响。

因此,建议在产品中提供两类模式:

代码语言:javascript
复制
实时性优先:低延迟 + 快速启动 + 小缓冲 + UDP优先
连续性优先:TCP传输 + 适当缓冲 + 更关注观看稳定性

创建播放View和启动播放

SmartMediaKit iOS播放器通过SDK创建播放View,再桥接为UIKit中的UIView使用:

代码语言:javascript
复制
_glView = (__bridge UIView *)([SmartPlayerSDK SmartPlayerCreatePlayView:
                              player_view_x_
                              y:player_view_y_
                              width:player_view_width_
                              height:player_view_height_
                              renderType:1]);

创建成功后加入当前页面:

代码语言:javascript
复制
[self.view addSubview:_glView];
[self.view sendSubviewToBack:_glView];

然后绑定给播放器实例:

代码语言:javascript
复制
NSInteger setViewRet =
    [_smart_player_sdk SmartPlayerSetPlayView:(__bridge void *)_glView];

最后启动播放:

代码语言:javascript
复制
NSInteger ret = [_smart_player_sdk SmartPlayerStart];
if (ret == DANIULIVE_RETURN_OK) {
    is_playing_ = YES;
}

停止播放时,必须先调用SDK停止接口:

代码语言:javascript
复制
NSInteger ret = [_smart_player_sdk SmartPlayerStop];

停止成功后,再释放播放View:

代码语言:javascript
复制
[_glView removeFromSuperview];

[SmartPlayerSDK SmartPlayeReleasePlayView:(__bridge void *)_glView];

_glView = nil;

这里需要特别强调:SDK创建的播放View应使用SDK对应的释放接口回收,不能只调用removeFromSuperview后直接丢弃对象引用。


三种解码模式:软解、普通硬解、Layer直显

Demo提供三种解码模式:

代码语言:javascript
复制
NSArray *decoderItems = @[@"软解", @"硬解", @"硬解(layer)"];
self.decoderModeSegment =
    [[UISegmentedControl alloc] initWithItems:decoderItems];
self.decoderModeSegment.selectedSegmentIndex = 1;

默认使用普通硬解模式。

软件解码

软件解码适合兼容性优先的场景。例如某些特殊码流、硬解不支持的编码参数、或者需要完整CPU图像处理链路时,可以选择软解。

软解的优势是可控性强,缺点是在高分辨率、高帧率场景下CPU占用和功耗会更高。

普通硬件解码

普通硬解使用VideoToolbox完成硬件解码,再交给普通渲染链路显示。

这个模式是Demo默认选择,也是多数项目中的推荐模式,因为它兼顾了性能和功能:

  • CPU占用更低;
  • 支持快照;
  • 支持亮度、对比度、饱和度调节;
  • 支持旋转和镜像;
  • 可以按需开启YUV回调;
  • 适合常规RTSP/RTMP直播预览。

硬解Layer直显

第三种模式是硬解Layer直显,对应:

代码语言:javascript
复制
NSInteger directRender = (selectedIndex == 2) ? 1 : 0;

[_smart_player_sdk SmartPlayerSetVideoDecoderMode:decoderMode];
[_smart_player_sdk SetDirectHardwareRender:directRender];

Layer直显模式使用AVSampleBufferDisplayLayer完成显示,减少普通渲染路径中的数据转换和回传,更偏向性能和低开销显示。

但它也有明确限制:由于视频图像不再回流到普通CPU图像处理链路,因此不支持:

  • YUV回调;
  • 快照;
  • 亮度调节;
  • 对比度调节;
  • 饱和度调节。

Demo在选择Layer模式后,会自动禁用快照按钮和色彩调节滑块:

代码语言:javascript
复制
BOOL supportsImageProcessing = (modeIndex != 2);

self.brightnessSlider.enabled = supportsImageProcessing;
self.contrastSlider.enabled = supportsImageProcessing;
self.saturationSlider.enabled = supportsImageProcessing;
resetColorBtn.enabled = supportsImageProcessing;
saveImageButton.enabled = supportsImageProcessing;

同时,为了避免播放过程中重建解码链和渲染链,Demo在播放期间禁用解码模式切换:

代码语言:javascript
复制
self.decoderModeSegment.enabled = NO;

停止播放后再恢复选择。


横竖屏适配和播放区域更新

iOS播放器如果要用于真实业务,横竖屏适配必须处理好。SmartiOSPlayerV2在viewDidLayoutSubviews中根据当前页面宽高判断方向:

代码语言:javascript
复制
CGFloat w = self.view.bounds.size.width;
CGFloat h = self.view.bounds.size.height;
BOOL isLandscape = w > h;

横屏模式下,视频全屏显示,隐藏控制面板:

代码语言:javascript
复制
videoFrame = CGRectMake(0, 0, w, h);
self.controlContainerView.hidden = YES;
self.backButton.hidden = NO;

竖屏模式下,上方40%用于显示视频,下方60%用于控制区:

代码语言:javascript
复制
CGFloat videoHeight = h * 0.4;
videoFrame = CGRectMake(0, 50, w, videoHeight);

CGRect controlFrame =
    CGRectMake(0, 50 + videoHeight, w, h - (50 + videoHeight));

当播放View存在时,更新其Frame:

代码语言:javascript
复制
if (_glView) {
    if (!CGRectEqualToRect(_glView.frame, videoFrame)) {
        _glView.frame = videoFrame;
    }
}

普通Metal渲染路径下,播放View内部会同步drawable尺寸,不需要业务层频繁主动刷新。

但如果使用Layer直显模式,横竖屏变化后需要主动通知SDK更新显示区域:

代码语言:javascript
复制
if (_smart_player_sdk && is_playing_ &&
    self.decoderModeSegment.selectedSegmentIndex == 2) {
    [_smart_player_sdk UpdateViewSize:player_view_width_
                                height:player_view_height_];
}

对于iOS 16及以上版本,Demo使用UIWindowSceneGeometryPreferencesIOS请求切回竖屏;旧版本则采用兼容方式处理方向切换。


按比例绘制和拉伸铺满

在监控和工业视觉场景中,很多时候需要保持原始比例,避免画面中人物、车辆、仪表或设备结构变形。

Demo提供“按比例绘制”开关:

代码语言:javascript
复制
self.renderAspectSwitch.on = YES;

播放中可动态切换:

代码语言:javascript
复制
NSInteger scaleMode = sender.isOn ? 1 : 0;

[_smart_player_sdk SmartPlayerSetRenderScaleMode:scaleMode];

这里的逻辑很适合实际项目参考:如果接口设置失败,UI开关会回滚到原状态,避免出现“界面显示已经切换,但底层实际没有切换”的状态不一致问题。


播放中动态切换URL

摄像机轮巡、主备流切换、导播预览、测试不同协议源时,经常需要在播放中切换URL。

Demo中提供了备用地址:

代码语言:javascript
复制
static NSString * const kSwitchPlaybackURL =
    @"rtmp://192.168.0.103:1935/hls/stream1";

播放中切换时调用:

代码语言:javascript
复制
NSInteger ret =
    [_smart_player_sdk SmartPlayerSwitchPlaybackUrl:newUrl];

if (ret == DANIULIVE_RETURN_OK) {
    is_switch_url_ = targetSwitchState;
    self.playbackURLTextField.text = newUrl;
}

这里需要区分两类场景:

代码语言:javascript
复制
修改输入框后重新启动:
适合完整更换播放源、重新走初始化配置。

播放中SwitchPlaybackUrl:
适合主备流切换、轮巡预览、临时切换测试源。

对于监控平台或移动导播工具来说,播放中切换URL可以减少用户等待,也能让Demo更容易扩展为多源预览工具。


音量、静音、旋转和镜像

播放器常用控制能力包括音量、静音、旋转和镜像。

音量调节:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetAudioVolume:
    (NSInteger)self.audioVolumeSlider.value];

静音切换:

代码语言:javascript
复制
BOOL targetMute = !is_mute_;

[_smart_player_sdk SmartPlayerSetMute:targetMute ? 1 : 0];

is_mute_ = targetMute;

画面旋转:

代码语言:javascript
复制
NSInteger targetRotation = (rotate_degrees_ + 90) % 360;

[_smart_player_sdk SmartPlayerSetRotation:targetRotation];

rotate_degrees_ = targetRotation;

水平镜像:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetFlipHorizontal:
    targetFlip ? 1 : 0];

垂直镜像:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetFlipVertical:
    targetFlip ? 1 : 0];

这些功能在实际项目中非常常见。例如:

  • 竖装摄像头需要旋转显示;
  • 前置摄像头或特殊采集设备需要镜像修正;
  • 移动端巡检时需要临时静音;
  • 会议或导播场景中需要快速调整预览方向。

亮度、对比度和饱和度调节

Demo在初始化阶段启用图像调节能力:

代码语言:javascript
复制
[_smart_player_sdk EnableVideoBrightnessOption:1];
[_smart_player_sdk EnableVideoContrastOption:1];
[_smart_player_sdk EnableVideoSaturationOption:1];

然后设置默认值:

代码语言:javascript
复制
[_smart_player_sdk SetVideoBrightness:50];
[_smart_player_sdk SetVideoContrast:50];
[_smart_player_sdk SetVideoSaturation:50];

滑块变化时动态设置:

代码语言:javascript
复制
NSInteger val = (NSInteger)self.brightnessSlider.value;

[_smart_player_sdk SetVideoBrightness:val];

对比度和饱和度同理。

这类能力在安防、工业视觉和户外图传场景中很有价值。比如摄像头逆光、夜间画面偏暗、图像颜色偏淡时,上层可以提供简单的画面增强入口,改善预览体验。

不过需要注意,Layer直显模式下不支持这类图像调节,因此Demo会在UI层主动禁用相关控件。


本地录像和录像回放

SmartiOSPlayerV2支持播放侧录像。录像目录使用App的Documents目录:

代码语言:javascript
复制
NSString *docDir =
    [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                         NSUserDomainMask,
                                         YES) firstObject];

[_smart_player_sdk SmartPlayerSetRecorderDirectory:docDir];

设置单个录像文件最大大小:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetRecorderFileMaxSize:500];

开启音频转AAC,并启用音视频录像:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSetRecorderAudioTranscodeAAC:1];
[_smart_player_sdk SmartPlayerSetRecorderVideo:1];
[_smart_player_sdk SmartPlayerSetRecorderAudio:1];

启动录像:

代码语言:javascript
复制
NSInteger ret = [_smart_player_sdk SmartPlayerStartRecorder];

停止录像:

代码语言:javascript
复制
NSInteger ret = [_smart_player_sdk SmartPlayerStopRecorder];

Demo中的录像和播放共用SDK实例,但播放状态和录像状态相互独立。这样可以支持:

  • 播放中开始录像;
  • 播放中停止录像;
  • 停止播放后按业务需要继续处理录像状态;
  • 播放和录像都结束后再释放SDK。

Demo还提供独立的RecorderView页面,用于本地录像文件回放。回放页可扫描Documents目录下的录像文件,并支持播放、暂停、进度拖动、倍速、循环播放、删除和横屏回放。

这让播放器Demo不仅能预览直播流,还能完成“实时查看 + 本地留档 + 本地回放”的完整闭环。


快照保存到系统相册

快照流程分为两步:

第一步,SDK将当前画面保存到App Documents目录中的临时PNG文件:

代码语言:javascript
复制
[_smart_player_sdk SmartPlayerSaveCurImage:fileName];

第二步,在快照事件回调中读取图片,并写入系统相册:

代码语言:javascript
复制
UIImage *snapshotImage =
    [UIImage imageWithContentsOfFile:param3];

UIImageWriteToSavedPhotosAlbum(snapshotImage,
                               self,
                               @selector(image:didFinishSavingWithError:contextInfo:),
                               (void *)CFBridgingRetain(param3));

Demo这里有一个值得注意的细节:临时图片路径通过contextInfo跟随本次相册写入操作传递。这样即使用户连续点击快照,每次写入完成后也只删除自己对应的临时文件,避免多个快照共用成员变量导致路径覆盖或误删。

回调完成后删除临时文件:

代码语言:javascript
复制
if (tmpImagePath) {
    [[NSFileManager defaultManager] removeItemAtPath:tmpImagePath error:nil];
}

保存到相册需要在Info.plist中配置照片写入权限说明:

代码语言:javascript
复制
<key>NSPhotoLibraryAddUsageDescription</key>
<string>用于保存直播画面快照</string>

正式项目中,权限文案建议写得更贴近业务,例如“用于保存监控画面截图”或“用于保存巡检过程中的视频快照”。


YUV回调:用于AI分析和自定义处理

Demo中保留了I420 YUV回调示例,但默认关闭:

代码语言:javascript
复制
static const BOOL kEnableYuvCallbackDemo = NO;

原因很清楚:普通硬解路径下开启YUV回调,通常需要从NV12转换到I420,并增加一次数据拷贝。对于只做正常播放的场景,默认开启会带来额外开销。

需要图像分析、自定义渲染或AI处理时,可以打开:

代码语言:javascript
复制
_smart_player_sdk.yuvDataBlock =
    ^(int width, int height, unsigned long long timeStamp,
      const unsigned char *yData,
      const unsigned char *uData,
      const unsigned char *vData,
      int yStride, int uStride, int vStride) {

        // 处理I420图像
    };

[_smart_player_sdk SmartPlayerSetYuvBlock:YES];

使用YUV回调时要注意:

  • 回调发生在SDK的视频输出线程;
  • Y、U、V指针只在本次Block返回前有效;
  • 异步处理必须在回调内按照stride拷贝数据;
  • 不要在回调线程执行耗时算法;
  • 不要在回调线程同步更新UI;
  • Layer直显模式不支持YUV回调。

因此,比较合理的方式是:回调中快速复制或入队,再交给独立工作线程做AI检测、图像分析或业务处理。


SEI用户数据回调

除了视频帧,直播流中还可能携带业务数据。例如:

  • 设备状态;
  • AI识别结果;
  • 业务时间戳;
  • GPS信息;
  • 告警事件;
  • 自定义控制消息。

Demo通过用户数据回调接收这类信息:

代码语言:javascript
复制
_smart_player_sdk.spUserDataCallBack =
    ^(int type,
      unsigned char *data,
      unsigned int size,
      unsigned long long ts,
      unsigned long long r1,
      long long r2,
      unsigned char *r3) {

        [strongSelf OnUserDataCallBack:type
                                  data:data
                                  size:(int)size];
    };

[_smart_player_sdk SmartPlayerSetUserDataCallback:YES];

OnUserDataCallBack中,Demo针对UTF-8字符串做了基本保护:

代码语言:javascript
复制
if (type == 2 && data != NULL && size > 0) {
    NSUInteger length = (NSUInteger)size;

    if (data[length - 1] == 0) {
        --length;
    }

    NSString *msg =
        [[NSString alloc] initWithBytes:data
                                 length:length
                               encoding:NSUTF8StringEncoding];
}

随后切回主线程更新UI。

这个能力对于“视频 + 业务数据同步”的场景很实用。例如无人机图传中同步飞行状态,工业检测中同步AI识别结果,远程巡检中同步设备编号、时间戳和异常信息。


事件回调和网络状态监控

直播播放器不能只关心“播放按钮是否按下”。实际项目更需要知道当前播放器处于什么状态:

  • 正在连接;
  • 已连接;
  • 连接失败;
  • 断开连接;
  • 停止播放;
  • 长时间无媒体数据;
  • 分辨率变化;
  • 下载速度;
  • 丢包率;
  • 录像新文件;
  • 录像文件完成;
  • 快照成功或失败。

Demo在handleSmartPlayerEvent中统一处理这些事件,并切回主线程更新UI:

代码语言:javascript
复制
dispatch_async(dispatch_get_main_queue(), ^{
    // 更新UILabel、UIButton等UIKit控件
});

下载速度事件中,param1表示Byte/s,Demo同时换算成kbps和KB/s:

代码语言:javascript
复制
NSInteger kbps = (NSInteger)param1 * 8 / 1000;
NSInteger kbs = (NSInteger)param1 / 1024;

丢包率通过param2解析:

代码语言:javascript
复制
const float Q8_SCALE = 256.0f;
const unsigned long long MASK_15_BIT = 0x7FFFULL;

if (((param2 >> 15) & 1ULL) == 1ULL) {
    float lr = (param2 & MASK_15_BIT) / Q8_SCALE;
}

这组数据对排查问题很有价值。

例如:

代码语言:javascript
复制
有下载速度,但画面不动:
可能是解码等待关键帧、码流异常或渲染链路问题。

没有下载速度:
可能是网络连接、服务端、URL或权限问题。

UDP丢包率较高:
可能需要切换TCP,或降低码率、帧率、分辨率。

播放器Demo提供这些状态,不只是为了显示好看,而是为了让开发者在真实网络环境中快速定位问题。


iOS工程配置和权限说明

本地网络权限

iOS 14及以上访问局域网设备时,需要配置本地网络权限说明:

代码语言:javascript
复制
<key>NSLocalNetworkUsageDescription</key>
<string>用于连接局域网内的直播设备</string>

如果RTSP服务器、IPC、NVR或测试推流设备位于局域网,首次访问时系统会弹出授权提示。用户拒绝后,播放器可能表现为连接失败或无数据。

照片写入权限

快照写入系统相册,需要配置:

代码语言:javascript
复制
<key>NSPhotoLibraryAddUsageDescription</key>
<string>用于保存直播画面快照</string>

文件共享

Demo开启UIFileSharingEnabled后,调试阶段可以通过系统文件共享方式查看App的Documents目录,便于导出录像文件。

屏幕方向

Demo支持iPhone和iPad横竖屏:

代码语言:javascript
复制
iPhone:竖屏、横屏左、横屏右
iPad:竖屏、倒置竖屏、横屏左、横屏右

XCFramework集成

将SDK对应的SmartPlayerSDKAll.xcframework加入工程,并根据SDK交付说明完成链接配置。

常见配置包括:

代码语言:javascript
复制
Embed:Do Not Embed
Other Linker Flags:-ObjC

Demo涉及的系统依赖通常包括:

代码语言:javascript
复制
AVFoundation.framework
AudioToolbox.framework
CFNetwork.framework
Security.framework
SystemConfiguration.framework
CoreFoundation.framework
CoreGraphics.framework
CoreMedia.framework
CoreVideo.framework
VideoToolbox.framework
Metal.framework
GLKit.framework
OpenGLES.framework
QuartzCore.framework

libz.tbd
libbz2.tbd
libiconv.tbd
libc++.tbd

具体依赖应以实际SDK版本和交付文档为准。

需要注意的是,如果Demo工程中保留了历史框架配置,正式项目中应结合当前代码和SDK依赖确认是否仍然需要,不建议无差别照搬旧工程配置。


实际项目接入建议

关键流程失败应立即终止

以下流程属于关键路径,失败后不建议继续启动:

代码语言:javascript
复制
SDK初始化失败
播放URL设置失败
解码模式设置失败
播放View创建失败
播放View绑定失败
SmartPlayerStart失败
录像目录设置失败

而音量、亮度、镜像、下载速度上报等附加配置失败,可以记录日志并继续播放,避免非核心功能阻断主流程。

TCP和UDP要按场景选择

RTSP TCP和UDP没有绝对优劣。

代码语言:javascript
复制
UDP:
实时性更好,适合局域网、专网、低延迟图传;
但弱网丢包后可能影响视频参考帧恢复。

TCP:
连续性更好,适合公网、跨网和观看稳定性优先场景;
但网络抖动时可能出现重传等待,增加延迟。

工程上建议不要把TCP/UDP写死,而是作为业务配置项提供给上层。

真机测试

模拟器适合验证UI布局和部分流程,但以下内容必须用真机验证:

  • VideoToolbox硬解;
  • 音频播放和音量控制;
  • Metal渲染性能;
  • 局域网权限;
  • 横竖屏切换;
  • 长时间播放稳定性;
  • 发热和功耗;
  • 弱网、断网、切网恢复。

YUV回调不要做重活

YUV回调适合做AI分析、自定义处理或第三方渲染,但不要在回调线程直接做耗时计算。

推荐方式:

代码语言:javascript
复制
YUV回调线程:快速校验 + 拷贝/入队
业务工作线程:AI分析/图像处理
主线程:只负责UI更新

播放View释放要谨慎

SDK创建的View不要随意释放。只有在停止播放成功,或SDK强制释放完成后,才能拆除播放View。

这对于避免偶发崩溃、黑屏和渲染线程异常非常重要。

后台播放需要单独设计

当前Demo重点展示前台RTSP/RTMP直播播放,没有把后台持续拉流、后台音频或画中画作为默认能力。

如果业务需要后台运行,应额外结合:

  • iOS Background Modes;
  • AVAudioSession;
  • App审核规则;
  • 功耗控制;
  • 网络保活策略;
  • 前后台切换时的播放器状态机。

总结

大牛直播SDK(SmartMediaKit)iOS版RTSP/RTMP播放模块,覆盖了移动端实时播放中最核心的工程能力:

  • RTSP/RTMP低延迟播放;
  • RTSP TCP/UDP传输切换;
  • H.264/H.265软硬解码;
  • VideoToolbox硬解;
  • Metal渲染;
  • AVSampleBufferDisplayLayer直显;
  • 横竖屏和iPhone/iPad适配;
  • 播放中动态切换URL;
  • 音量、静音、旋转、镜像和比例绘制;
  • 亮度、对比度、饱和度调节;
  • 本地录像和录像回放;
  • 快照保存到系统相册;
  • I420 YUV回调;
  • SEI用户数据回调;
  • 下载速度、丢包率和播放事件回调;
  • 播放、录像和SDK实例的独立生命周期管理;
  • UIKit线程安全和旧实例事件过滤。

SmartiOSPlayerV2 Demo的意义,不只是演示SDK接口怎么调,而是给出了一个接近真实项目的播放器集成参考。

对于正在开发移动安防、工业巡检、无人机图传、机器人远程操控、教育直播预览或流媒体测试工具的团队,可以基于该Demo快速构建iOS端RTSP/RTMP直播播放模块,再结合自身业务UI、网络策略和状态机进行二次封装。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 移动端直播播放器,不只是播放一个URL
  • Demo整体能力概览
  • 典型使用场景
    • 移动安防监控
    • 无人机、机器人和远程操控
    • 工业巡检和远程运维
    • 教育直播和移动导播
  • 播放器状态设计:播放、录像和SDK实例分离
  • 初始化播放器和低延迟参数配置
  • 创建播放View和启动播放
  • 三种解码模式:软解、普通硬解、Layer直显
    • 软件解码
    • 普通硬件解码
    • 硬解Layer直显
  • 横竖屏适配和播放区域更新
  • 按比例绘制和拉伸铺满
  • 播放中动态切换URL
  • 音量、静音、旋转和镜像
  • 亮度、对比度和饱和度调节
  • 本地录像和录像回放
  • 快照保存到系统相册
  • YUV回调:用于AI分析和自定义处理
  • SEI用户数据回调
  • 事件回调和网络状态监控
  • iOS工程配置和权限说明
    • 本地网络权限
    • 照片写入权限
    • 文件共享
    • 屏幕方向
    • XCFramework集成
  • 实际项目接入建议
    • 关键流程失败应立即终止
    • TCP和UDP要按场景选择
    • 真机测试
    • YUV回调不要做重活
    • 播放View释放要谨慎
    • 后台播放需要单独设计
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档