当前位置: 首页 > news >正文

iOS上使用WebRTC推拉流的案例

一、库集成

首先,确保在你的 Podfile 中添加依赖:

pod 'GoogleWebRTC'

然后执行 pod install 安装库。

二、代码示例

2.1、权限配置:在 Info.plist 中添加摄像头、麦克风权限

<!-- 需要在 Info.plist 中添加以下权限 -->
<key>NSCameraUsageDescription</key>
<string>需要访问摄像头进行视频通话</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风进行语音通话</string>
<key>NSAppTransportSecurity</key>
<dict><key>NSAllowsArbitraryLoads</key><true/>
</dict>

2.2、代码
核心类oc:WebRTCManager.m

#import "WebRTCManager.h"
#import <AVFoundation/AVFoundation.h>@interface WebRTCManager ()
@property (nonatomic, strong) RTCPeerConnectionFactory *factory;
@property (nonatomic, strong) RTCVideoTrack *localVideoTrack;
@property (nonatomic, strong) RTCAudioTrack *localAudioTrack;
@property (nonatomic, strong) RTCVideoRendererAdapter *localRenderer;
@end@implementation WebRTCManager- (instancetype)initWithDelegate:(id<WebRTCManagerDelegate>)delegate {self = [super init];if (self) {_delegate = delegate;[self setupPeerConnectionFactory];[self setupPeerConnection];}return self;
}// 初始化 PeerConnection 工厂
- (void)setupPeerConnectionFactory {RTCInitializeSSL();_factory = [[RTCPeerConnectionFactory alloc] init];
}// 配置并创建 PeerConnection
- (void)setupPeerConnection {RTCConfiguration *config = [[RTCConfiguration alloc] init];config.iceServers = @[[[RTCIceServer alloc] initWithURLStrings:@[@"stun:stun.l.google.com:19302"]]];RTCOfferAnswerOptions *offerAnswerOptions = [[RTCOfferAnswerOptions alloc] init];offerAnswerOptions.offerToReceiveAudio = YES;offerAnswerOptions.offerToReceiveVideo = YES;RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[] optionalConstraints:@[]];_peerConnection = [_factory peerConnectionWithConfiguration:config constraints:constraints delegate:self];
}// 初始化本地媒体流
- (void)setupLocalStreamWithVideoView:(UIView *)videoView {// 请求音视频权限[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {dispatch_async(dispatch_get_main_queue(), ^{if (granted) {[self createLocalMediaStream];[self setupLocalVideoRender:videoView];} else {NSLog(@"需要摄像头权限才能使用视频功能");}});}];
}// 创建本地媒体流
- (void)createLocalMediaStream {_localStream = [_factory mediaStreamWithStreamId:@"localStream"];// 创建音频轨道RTCAudioSource *audioSource = [_factory audioSourceWithConstraints:[self defaultMediaConstraints]];_localAudioTrack = [_factory audioTrackWithSource:audioSource trackId:@"audio0"];[_localStream addAudioTrack:_localAudioTrack];// 创建视频轨道RTCVideoSource *videoSource = [_factory videoSourceWithConstraints:[self videoConstraints]];_localVideoTrack = [_factory videoTrackWithSource:videoSource trackId:@"video0"];[_localStream addVideoTrack:_localVideoTrack];// 将本地流添加到 PeerConnection[_peerConnection addStream:_localStream];
}// 设置本地视频渲染
- (void)setupLocalVideoRender:(UIView *)videoView {RTCMTLVideoView *rendererView = [[RTCMTLVideoView alloc] init];rendererView.frame = videoView.bounds;rendererView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;[videoView addSubview:rendererView];_localRenderer = [[RTCVideoRendererAdapter alloc] initWithRenderer:rendererView];[_localVideoTrack addRenderer:_localRenderer];
}// 创建 Offer
- (void)createOffer {RTCOfferAnswerConstraints *constraints = [[RTCOfferAnswerConstraints alloc] init];constraints.mandatoryConstraints = @[[[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],[[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]];[_peerConnection createOfferWithConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {if (error) {NSLog(@"创建 Offer 失败: %@", error.localizedDescription);return;}[self.peerConnection setLocalDescriptionWithSessionDescription:sdp completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"设置本地描述失败: %@", error.localizedDescription);return;}// 这里应该将 sdp 发送给信令服务器NSString *offerString = [self sessionDescriptionToString:sdp];NSLog(@"生成 Offer: %@", offerString);// [self.signalingClient sendOffer:offerString];}];}];
}// 处理远程 Offer
- (void)handleRemoteOffer:(NSString *)offer {RTCSessionDescription *remoteSDP = [self stringToSessionDescription:offer type:RTCSdpTypeOffer];[_peerConnection setRemoteDescriptionWithSessionDescription:remoteSDP completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"设置远程 Offer 失败: %@", error.localizedDescription);return;}// 创建 Answer[self createAnswer];}];
}// 创建 Answer
- (void)createAnswer {RTCOfferAnswerConstraints *constraints = [[RTCOfferAnswerConstraints alloc] init];[_peerConnection createAnswerWithConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {if (error) {NSLog(@"创建 Answer 失败: %@", error.localizedDescription);return;}[self.peerConnection setLocalDescriptionWithSessionDescription:sdp completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"设置本地 Answer 失败: %@", error.localizedDescription);return;}// 将 Answer 发送给信令服务器NSString *answerString = [self sessionDescriptionToString:sdp];NSLog(@"生成 Answer: %@", answerString);// [self.signalingClient sendAnswer:answerString];}];}];
}// 处理远程 Answer
- (void)handleRemoteAnswer:(NSString *)answer {RTCSessionDescription *remoteSDP = [self stringToSessionDescription:answer type:RTCSdpTypeAnswer];[_peerConnection setRemoteDescriptionWithSessionDescription:remoteSDP completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"设置远程 Answer 失败: %@", error.localizedDescription);}}];
}// 处理远程 ICE 候选者
- (void)handleRemoteICECandidate:(NSString *)candidate {NSDictionary *candidateDict = [NSJSONSerialization JSONObjectWithData:[candidate dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];if (!candidateDict) return;RTCIceCandidate *iceCandidate = [[RTCIceCandidate alloc] initWithSdpMLineIndex:[candidateDict[@"sdpMLineIndex"] integerValue]sdpMid:candidateDict[@"sdpMid"]sdp:candidateDict[@"sdp"]];[_peerConnection addIceCandidate:iceCandidate completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"添加远程 ICE 候选者失败: %@", error.localizedDescription);}}];
}// 断开连接
- (void)disconnect {[_peerConnection close];_peerConnection = nil;[_localStream removeAllAudioTracks];[_localStream removeAllVideoTracks];_localStream = nil;
}#pragma mark - RTCPeerConnectionDelegate// 收到远程媒体流
- (void)peerConnection:(RTCPeerConnection *)peerConnection didAddStream:(RTCMediaStream *)stream {NSLog(@"收到远程媒体流");if ([self.delegate respondsToSelector:@selector(didReceiveRemoteStream:)] && stream) {[self.delegate didReceiveRemoteStream:stream];}
}// 生成 ICE 候选者
- (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate {NSDictionary *candidateDict = @{@"sdpMLineIndex": @(candidate.sdpMLineIndex),@"sdpMid": candidate.sdpMid ?: @"",@"sdp": candidate.sdp};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:candidateDict options:0 error:nil];NSString *candidateString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];// 将 ICE 候选者发送给信令服务器NSLog(@"生成 ICE 候选者: %@", candidateString);// [self.signalingClient sendICECandidate:candidateString];
}#pragma mark - 工具方法// 媒体约束配置
- (RTCMediaConstraints *)defaultMediaConstraints {return [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[] optionalConstraints:@[]];
}// 视频约束配置
- (RTCMediaConstraints *)videoConstraints {return [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[[[RTCPair alloc] initWithKey:@"maxWidth" value:@"1280"],[[RTCPair alloc] initWithKey:@"maxHeight" value:@"720"],[[RTCPair alloc] initWithKey:@"maxFrameRate" value:@"30"]] optionalConstraints:@[]];
}// 将 SessionDescription 转换为字符串
- (NSString *)sessionDescriptionToString:(RTCSessionDescription *)sdp {NSDictionary *dict = @{@"type": [self sdpTypeToString:sdp.type],@"sdp": sdp.sdp};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}// 将字符串转换为 SessionDescription
- (RTCSessionDescription *)stringToSessionDescription:(NSString *)string type:(RTCSdpType)type {NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];return [[RTCSessionDescription alloc] initWithType:type sdp:dict[@"sdp"]];
}// SDP 类型转换
- (NSString *)sdpTypeToString:(RTCSdpType)type {switch (type) {case RTCSdpTypeOffer: return @"offer";case RTCSdpTypeAnswer: return @"answer";case RTCSdpTypePrAnswer: return @"pranswer";case RTCSdpTypeRollback: return @"rollback";default: return @"";}
}@end

调用OC:ViewController.m

#import "ViewController.h"
#import <WebRTC/WebRTC.h>@interface ViewController ()
@property (nonatomic, strong) WebRTCManager *rtcManager;
@property (nonatomic, strong) UIView *localVideoView;
@property (nonatomic, strong) UIView *remoteVideoView;
@property (nonatomic, strong) UIButton *connectButton;
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = UIColor.whiteColor;[self setupUI];[self setupWebRTC];
}- (void)setupUI {// 本地视频视图_localVideoView = [[UIView alloc] init];_localVideoView.backgroundColor = UIColor.lightGrayColor;_localVideoView.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_localVideoView];// 远程视频视图_remoteVideoView = [[UIView alloc] init];_remoteVideoView.backgroundColor = UIColor.darkGrayColor;_remoteVideoView.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_remoteVideoView];// 连接按钮_connectButton = [UIButton buttonWithType:UIButtonTypeSystem];[_connectButton setTitle:@"建立连接" forState:UIControlStateNormal];[_connectButton addTarget:self action:@selector(connectButtonTapped) forControlEvents:UIControlEventTouchUpInside];_connectButton.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_connectButton];// 布局[NSLayoutConstraint activateConstraints:@[// 本地视频(右上角小窗口)[_localVideoView topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:20],[_localVideoView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20],[_localVideoView.widthAnchor constraintEqualToConstant:120],[_localVideoView.heightAnchor constraintEqualToConstant:180],// 远程视频(全屏)[_remoteVideoView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],[_remoteVideoView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],[_remoteVideoView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],[_remoteVideoView.bottomAnchor constraintEqualToAnchor:self.connectButton.topAnchor constant:-20],// 连接按钮[_connectButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-20],[_connectButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],[_connectButton.widthAnchor constraintEqualToConstant:120],[_connectButton.heightAnchor constraintEqualToConstant:44]]];
}- (void)setupWebRTC {_rtcManager = [[WebRTCManager alloc] initWithDelegate:self];[_rtcManager setupLocalStreamWithVideoView:self.localVideoView];
}- (void)connectButtonTapped {[_rtcManager createOffer];
}#pragma mark - WebRTCManagerDelegate// 处理收到的远程流
- (void)didReceiveRemoteStream:(RTCMediaStream *)stream {dispatch_async(dispatch_get_main_queue(), ^{// 渲染远程视频if (stream.videoTracks.count > 0) {RTCVideoTrack *remoteVideoTrack = stream.videoTracks[0];RTCMTLVideoView *rendererView = [[RTCMTLVideoView alloc] init];rendererView.frame = self.remoteVideoView.bounds;rendererView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;[self.remoteVideoView addSubview:rendererView];[remoteVideoTrack addRenderer:[[RTCVideoRendererAdapter alloc] initWithRenderer:rendererView]];}});
}- (void)dealloc {[_rtcManager disconnect];
}@end

代码说明
这个示例实现了 iOS 平台上基于 WebRTC 的基本音视频推拉流功能,主要包含以下部分:
WebRTCManager:核心管理类,负责:

  • 初始化 WebRTC 相关组件
  • 处理本地音视频流的捕获和预览
  • 管理 PeerConnection 连接
  • 处理 SDP 交换和 ICE 候选者

ViewController:界面控制器,负责:

  • 创建本地和远程视频的预览视图
  • 处理用户交互(如建立连接)
  • 渲染远程视频流

三、使用说明

这个示例缺少信令服务器的实现,你需要自己搭建一个信令服务器
在实际使用中,需要将代码中注释掉的信令发送部分替换为实际的网络请求
代码中的 STUN 服务器使用了 Google 的公共服务器,生产环境中建议使用自己的 STUN/TURN 服务器

四、扩展建议

  • 添加错误处理和重连机制
  • 实现多人通话功能
  • 添加视频质量控制
  • 实现屏幕共享功能
  • 添加音频 /video 开关控制

这个示例仅仅提供了基础的推拉流框架,你可以根据实际需求进行扩展和优化。

http://www.lryc.cn/news/597087.html

相关文章:

  • 想曰加密工具好用吗?本地安全、支持多算法的加密方案详解
  • ZLMediaKit流媒体服务器WebRTC页面显示:使用docker部署
  • 基于Matlab传统图像处理技术的车辆车型识别与分类方法研究
  • 【第三章自定义检视面板_创建自定义编辑器_如何创建自定义PropertyDrawer(9/9)】
  • 第六章 W55MH32 UDP Multicast示例
  • 在离线 Ubuntu 22.04机器上运行 ddkj_portainer-cn 镜像 其他相关操作也可以复刻 docker
  • CCD工业相机系统设计——基于FPGA设计
  • 【后端】FastAPI的Pydantic 模型
  • 【Linux-云原生-笔记】keepalived相关
  • 蒙牛社交电商的升级路径研究:基于开源链动2+1模式、AI智能名片与S2B2C商城小程序源码的融合创新
  • 轻量化RTSP视频通路实践:采集即服务、播放即模块的工程解读
  • 【Redis】在Ubentu环境下安装Redis
  • RCE随笔-奇技淫巧(2)
  • 【Linux-云原生-笔记】Haproxy相关
  • ros0基础-day18
  • OCP NIC 3.0 Ethernet的multiroot complex和multi host complex的区别
  • Android多开实现方案深度分析
  • 【硬件】Fan in和Fan out
  • RAG深入理解和简易实现
  • 海信IP501H-IP502h_GK6323处理器-原机安卓9专用-优盘卡刷固件包
  • springcloud环境和工程搭建
  • 中国多媒体与网络教学学报编辑部中国多媒体与网络教学学报杂志社2025年第6期目录
  • 论文略读:Mitigating Catastrophic Forgetting in Language Transfer via Model Merging
  • 旋变调零技术介绍与方法
  • CVE-2025-32463漏洞:sudo权限提升漏洞全解析
  • 「源力觉醒 创作者计划」深度讲解大模型之在百花齐放的大模型时代看百度文心大模型4.5的能力与未来
  • JS进阶学习
  • 《计算机网络》实验报告七 HTTP协议分析与测量
  • spring-cloud概述
  • 计算机网络学习----域名解析