WebRTC iOS平台的基本实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebRTC iOS平台的基本实现相关的知识,希望对你有一定的参考价值。

前面介绍了如何下载编译WebRTC,现在介绍如何利用WebRTC在ios客户端上简单实现音视频通话。

对下载编译还有问题的,请先查看:WebRTC(iOS)下载编译

不需要下载源码只需要库文件的也可以用CocoaPods下载编译好的库:pod ‘libjingle_peerconnection‘

开始编写之前,我们首先要搭建一个服务器,此服务器主要用于信令交互。我们这里采用github上的开源项目:SkyRTC项目

完整Demo下载:FLWebRTCDemo

开始iOS客户端的实现:

1、引入编译出的WebRTC库文件

2、引入必要的依赖库

AudioToolbox.framework

VideoToolbox.framework

QuartzCore.framework

OpenGLES.framework

CoreGraphics.framework

CoreVideo.framework

CoreMedia.framework

CoreAudio.framework

AVFoundation.framework

GLKit.framework

CFNetwork.framework

Security.framework

libsqlite3.tbd

libicucore.tbd

libc.tbd

libstdc++.6.0.9.tbd

3、代码实现音视频通话

//
// WebRTCHelper.h
// WebRTCDemo
//

#import <Foundation/Foundation.h>
#import <SocketRocket.h>
#import "RTCMediaStream.h"
#import "RTCPeerConnectionFactory.h"
#import "RTCPeerConnection.h"
#import "RTCPair.h"
#import "RTCMediaConstraints.h"
#import "RTCAudioTrack.h"
#import "RTCVideoTrack.h"
#import "RTCVideoCapturer.h"
#import "RTCSessionDescription.h"
#import "RTCSessionDescriptionDelegate.h"
#import "RTCEAGLVideoView.h"
#import "RTCICEServer.h"
#import "RTCVideoSource.h"
#import "RTCAVFoundationVideoSource.h"
#import "RTCICECandidate.h"
@protocol WebRTCHelperDelegate;
@interface WebRTCHelper : NSObject<SRWebSocketDelegate>
@property (nonatomic, assign)id<WebRTCHelperDelegate> delegate;
/**
* 与服务器建立连接
*
* @param server 服务器地址
* @param room 房间号
*/
- (void)connectServer:(NSString *)server room:(NSString *)room;
/**
* 退出房间
*/
- (void)exitRoom;
@end
@protocol WebRTCHelperDelegate <NSObject>
@optional
- (void)webRTCHelper:(WebRTCHelper *)webRTChelper setLocalStream:(RTCMediaStream *)stream userId:(NSString *)userId;
- (void)webRTCHelper:(WebRTCHelper *)webRTChelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId;
- (void)webRTCHelper:(WebRTCHelper *)webRTChelper closeWithUserId:(NSString *)userId;
@end 

 

 

//
// WebRTCHelper.m
// WebRTCDemo
//


#import "WebRTCHelper.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
typedef enum : NSUInteger {
RoleCaller,
RoleCallee,
} Role;
@interface WebRTCHelper ()<RTCPeerConnectionDelegate, RTCSessionDescriptionDelegate>
@end
@implementation WebRTCHelper
{
SRWebSocket *_socket;
NSString *_server;
NSString *_room;
RTCPeerConnectionFactory *_factory;
RTCMediaStream *_localStream;
NSString *_myId;
NSMutableDictionary *_connectionDic;
NSMutableArray *_connectionIdArray;
NSString *_currentId;
Role _role;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_connectionDic = [NSMutableDictionary dictionary];
_connectionIdArray = [NSMutableArray array];
}
return self;
}
/**
* 与服务器建立连接
*
* @param server 服务器地址
* @param room 房间号
*/
- (void)connectServer:(NSString *)server room:(NSString *)room
{
_server = server;
_room = room;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:3000",server]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
_socket = [[SRWebSocket alloc] initWithURLRequest:request];
_socket.delegate = self;
[_socket open];
}
/**
* 加入房间
*
* @param room 房间号
*/
- (void)joinRoom:(NSString *)room
{
if (_socket.readyState == SR_OPEN)
{
NSDictionary *dic = @{@"eventName": @"__join", @"data": @{@"room": room}};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[_socket send:data];
}
}
/**
* 退出房间
*/
- (void)exitRoom
{
_localStream = nil;
[_connectionIdArray enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self closePeerConnection:obj];
}];
[_socket close];
}
/**
* 关闭peerConnection
*
* @param connectionId <#connectionId description#>
*/
- (void)closePeerConnection:(NSString *)connectionId
{
RTCPeerConnection *peerConnection = [_connectionDic objectForKey:connectionId];
if (peerConnection)
{
[peerConnection close];
}
[_connectionIdArray removeObject:connectionId];
[_connectionDic removeObjectForKey:connectionId];
dispatch_async(dispatch_get_main_queue(), ^{
if ([_delegate respondsToSelector:@selector(webRTCHelper:closeWithUserId:)])
{
[_delegate webRTCHelper:self closeWithUserId:connectionId];
}
});
}
/**
* 创建本地流
*/
- (void)createLocalStream
{
_localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
//音频
RTCAudioTrack *audioTrack = [_factory audioTrackWithID:@"ARDAMSa0"];
[_localStream addAudioTrack:audioTrack];
//视频
NSArray *deviceArray = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *device = [deviceArray lastObject];
//检测摄像头权限
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
{
NSLog(@"相机访问受限");
if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
{
[_delegate webRTCHelper:self setLocalStream:nil userId:_myId];
}
}
else
{
if (device)
{
RTCVideoCapturer *capturer = [RTCVideoCapturer capturerWithDeviceName:device.localizedName];
RTCVideoSource *videoSource = [_factory videoSourceWithCapturer:capturer constraints:[self localVideoConstraints]];
RTCVideoTrack *videoTrack = [_factory videoTrackWithID:@"ARDAMSv0" source:videoSource];
[_localStream addVideoTrack:videoTrack];
if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
{
[_delegate webRTCHelper:self setLocalStream:_localStream userId:_myId];
}
}
else
{
NSLog(@"该设备不能打开摄像头");
if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
{
[_delegate webRTCHelper:self setLocalStream:nil userId:_myId];
}
}
}
}
/**
* 视频的相关约束
*/
- (RTCMediaConstraints *)localVideoConstraints
{
RTCPair *maxWidth = [[RTCPair alloc] initWithKey:@"maxWidth" value:@"640"];
RTCPair *minWidth = [[RTCPair alloc] initWithKey:@"minWidth" value:@"640"];
RTCPair *maxHeight = [[RTCPair alloc] initWithKey:@"maxHeight" value:@"480"];
RTCPair *minHeight = [[RTCPair alloc] initWithKey:@"minHeight" value:@"480"];
RTCPair *minFrameRate = [[RTCPair alloc] initWithKey:@"minFrameRate" value:@"15"];
NSArray *mandatory = @[maxWidth, minWidth, maxHeight, minHeight, minFrameRate];
RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory optionalConstraints:nil];
return constraints;
}
/**
* 为所有连接创建offer
*/
- (void)createOffers
{
[_connectionDic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
_currentId = key;
_role = RoleCaller;
[obj createOfferWithDelegate:self constraints:[self offerOranswerConstraint]];
}];
}
/**
* 为所有连接添加流
*/
- (void)addStreams
{
[_connectionDic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
if (!_localStream)
{
[self createLocalStream];
}
[obj addStream:_localStream];
}];
}
/**
* 创建所有连接
*/
- (void)createPeerConnections
{
[_connectionIdArray enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
RTCPeerConnection *connection = [self createPeerConnection:obj];
[_connectionDic setObject:connection forKey:obj];
}];
}
/**
* 创建点对点连接
*
* @param connectionId <#connectionId description#>
*
* @return <#return value description#>
*/
- (RTCPeerConnection *)createPeerConnection:(NSString *)connectionId
{
if (!_factory)
{
[RTCPeerConnectionFactory initializeSSL];
_factory = [[RTCPeerConnectionFactory alloc] init];
}
RTCPeerConnection *connection = [_factory peerConnectionWithICEServers:nil constraints:[self peerConnectionConstraints] delegate:self];
return connection;
}
/**
* peerConnection约束
*
* @return <#return value description#>
*/
- (RTCMediaConstraints *)peerConnectionConstraints
{
RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil optionalConstraints:@[[[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]]];
return constraints;
}
/**
* 设置offer/answer的约束
*/
- (RTCMediaConstraints *)offerOranswerConstraint
{
NSMutableArray *array = [NSMutableArray array];
RTCPair *receiveAudio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
[array addObject:receiveAudio];
NSString *video = @"true";
RTCPair *receiveVideo = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:video];
[array addObject:receiveVideo];
RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:array optionalConstraints:nil];
return constraints;
}
#pragma mark--RTCSessionDescriptionDelegate
// Called when creating a session.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
didCreateSessionDescription:(RTCSessionDescription *)sdp
error:(NSError *)error
{
NSLog(@"%s",__func__);
[peerConnection setLocalDescriptionWithDelegate:self sessionDescription:sdp];
}
// Called when setting a local or remote description.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
didSetSessionDescriptionWithError:(NSError *)error
{
NSLog(@"%s",__func__);
if (peerConnection.signalingState == RTCSignalingHaveRemoteOffer)
{
[peerConnection createAnswerWithDelegate:self constraints:[self offerOranswerConstraint]];
}
else if (peerConnection.signalingState == RTCSignalingHaveLocalOffer)
{
if (_role == RoleCallee)
{
NSDictionary *dic = @{@"eventName": @"__answer", @"data": @{@"sdp": @{@"type": @"answer", @"sdp": peerConnection.localDescription.description}, @"socketId": _currentId}};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[_socket send:data];
}
else if(_role == RoleCaller)
{
NSDictionary *dic = @{@"eventName": @"__offer", @"data": @{@"sdp": @{@"type": @"offer", @"sdp": peerConnection.localDescription.description}, @"socketId": _currentId}};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[_socket send:data];
}
}
else if (peerConnection.signalingState == RTCSignalingStable)
{
if (_role == RoleCallee)
{
NSDictionary *dic = @{@"eventName": @"__answer", @"data": @{@"sdp": @{@"type": @"answer", @"sdp": peerConnection.localDescription.description}, @"socketId": _currentId}};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[_socket send:data];
}
}
}
#pragma mark--RTCPeerConnectionDelegate
// Triggered when the SignalingState changed.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
signalingStateChanged:(RTCSignalingState)stateChanged
{
NSLog(@"%s",__func__);
NSLog(@"%d", stateChanged);
}
// Triggered when media is received on a new stream from remote peer.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
addedStream:(RTCMediaStream *)stream
{
NSLog(@"%s",__func__);
dispatch_async(dispatch_get_main_queue(), ^{
if ([_delegate respondsToSelector:@selector(webRTCHelper:addRemoteStream:userId:)])
{
[_delegate webRTCHelper:self addRemoteStream:stream userId:_currentId];
}
});
}
// Triggered when a remote peer close a stream.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
removedStream:(RTCMediaStream *)stream
{
NSLog(@"%s",__func__);
}
// Triggered when renegotiation is needed, for example the ICE has restarted.
- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection *)peerConnection
{
NSLog(@"%s",__func__);
}
// Called any time the ICEConnectionState changes.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
iceConnectionChanged:(RTCICEConnectionState)newState
{
NSLog(@"%s",__func__);
NSLog(@"%d", newState);
}
// Called any time the ICEGatheringState changes.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
iceGatheringChanged:(RTCICEGatheringState)newState
{
NSLog(@"%s",__func__);
NSLog(@"%d", newState);
}
// New Ice candidate have been found.
- (void)peerConnection:(RTCPeerConnection *)peerConnection
gotICECandidate:(RTCICECandidate *)candidate
{
NSLog(@"%s",__func__);
NSDictionary *dic = @{@"eventName": @"__ice_candidate", @"data": @{@"label": [NSNumber numberWithInteger:candidate.sdpMLineIndex], @"candidate": candidate.sdp, @"socketId": _currentId}};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[_socket send:data];
}
// New data channel has been opened.
- (void)peerConnection:(RTCPeerConnection*)peerConnection didOpenDataChannel:(RTCDataChannel*)dataChannel
{
NSLog(@"%s",__func__);
}
#pragma mark--SRWebSocketDelegate
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
NSLog(@"收到服务器消息:%@",message);
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
NSString *eventName = dic[@"eventName"];
if ([eventName isEqualToString:@"_peers"])
{
NSDictionary *dataDic = dic[@"data"];
NSArray *connections = dataDic[@"connections"];
[_connectionIdArray addObjectsFromArray:connections];
_myId = dataDic[@"you"];
if (!_factory)
{
[RTCPeerConnectionFactory initializeSSL];
_factory = [[RTCPeerConnectionFactory alloc] init];
}
if (!_localStream)
{
[self createLocalStream];
}
[self createPeerConnections];
[self addStreams];
[self createOffers];
}
else if ([eventName isEqualToString:@"_ice_candidate"])
{
NSDictionary *dataDic = dic[@"data"];
NSString *socketId = dataDic[@"socketId"];
NSInteger sdpMLineIndex = [dataDic[@"label"] integerValue];
NSString *sdp = dataDic[@"candidate"];
RTCICECandidate *candidate = [[RTCICECandidate alloc] initWithMid:nil index:sdpMLineIndex sdp:sdp];
RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
[peerConnection addICECandidate:candidate];
}
else if ([eventName isEqualToString:@"_new_peer"])
{
NSDictionary *dataDic = dic[@"data"];
NSString *socketId = dataDic[@"socketId"];
RTCPeerConnection *peerConnection = [self createPeerConnection:socketId];
if (!_localStream)
{
[self createLocalStream];
}
[peerConnection addStream:_localStream];
[_connectionIdArray addObject:socketId];
[_connectionDic setObject:peerConnection forKey:socketId];
}
else if ([eventName isEqualToString:@"_remove_peer"])
{
NSDictionary *dataDic = dic[@"data"];
NSString *socketId = dataDic[@"socketId"];
[self closePeerConnection:socketId];
}
else if ([eventName isEqualToString:@"_offer"])
{
NSDictionary *dataDic = dic[@"data"];
NSDictionary *sdpDic = dataDic[@"sdp"];
NSString *sdp = sdpDic[@"sdp"];
NSString *type = sdpDic[@"type"];
NSString *socketId = dataDic[@"socketId"];
RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
RTCSessionDescription *remoteSdp = [[RTCSessionDescription alloc] initWithType:type sdp:sdp];
[peerConnection setRemoteDescriptionWithDelegate:self sessionDescription:remoteSdp];
_currentId = socketId;
_role = RoleCallee;
}
else if ([eventName isEqualToString:@"_answer"])
{
NSDictionary *dataDic = dic[@"data"];
NSDictionary *sdpDic = dataDic[@"sdp"];
NSString *sdp = sdpDic[@"sdp"];
NSString *type = sdpDic[@"type"];
NSString *socketId = dataDic[@"socketId"];
RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
RTCSessionDescription *remoteSdp = [[RTCSessionDescription alloc] initWithType:type sdp:sdp];
[peerConnection setRemoteDescriptionWithDelegate:self sessionDescription:remoteSdp];
}
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
NSLog(@"websocket建立成功");
[self joinRoom:_room];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
NSLog(@"%ld:%@",(long)error.code, error.localizedDescription);
// [[[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"%ld:%@",(long)error.code, error.localizedDescription] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
NSLog(@"%s",__func__);
NSLog(@"%ld:%@",(long)code, reason);
// [[[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"%ld:%@",(long)code, reason] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
}
@end 

以上是关于WebRTC iOS平台的基本实现的主要内容,如果未能解决你的问题,请参考以下文章

跨平台视频通信项目-OpenTok

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

IOS技术分享| 在iOS WebRTC 中添加美颜滤镜

EasyNVR平台视频流在iOS端显示黑屏,无法正常播放是什么原因?

webrtc linphone 到底哪个