本文主要是整理了使用WebRTC做音视频通讯时的各知识点及问题点。有理解不足和不到位的地方也欢迎指正。 对于你感兴趣的部分可以选择性观看。
在使用WebRTC的库之前,需要对WebRTC进行初始化, 用到的代码如下:
RTCInitializeSSL();
转定义后可以看到方法的声明:
/**
* Initialize and clean up the SSL library. Failure is fatal. These call the
* corresponding functions in webrtc/rtc_base/ssladapter.h.
*/
RTC_EXTERN BOOL RTCInitializeSSL(void);
RTC_EXTERN BOOL RTCCleanupSSL(void);
Initialize and clean up the SSL library. Failure is fatal. 初始化SSL库,失败是致命的。
函数返回的是一个布尔类型, 表示初始化的结果。 如果失败,则不能继续使用其他特性。这是使用WebRTC的前提
在 WebRTC Native 层,factory 可以说是 “万物的根源”,像 RTCVideoSource、RTCVideoTrack、RTCPeerConnection这些类型的对象,都需要通过 factory 来创建
[RTCPeerConnectionFactory initialize];
//如果点对点工厂为空
if (!factory)
{
RTCDefaultVideoDecoderFactory* decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];
RTCDefaultVideoEncoderFactory* encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];
NSArray* codecs = [encoderFactory supportedCodecs];
[encoderFactory setPreferredCodec:codecs[2]];
factory = [[RTCPeerConnectionFactory alloc] initWithEncoderFactory: encoderFactory
decoderFactory: decoderFactory];
}
- 首先要调用
RTCPeerConnectionFactory类的initialize方法进行初始化;- 然后创建 factory 对象。需要注意的是,在创建 factory 对象时,传入了两个参数:一个是默认的
编码器;一个是默认的解码器。我们可以通过修改这两个参数来达到使用不同编解码器的目的。
在获取视频之前,我们首先要选择使用哪个视频设备采集数据。在WebRTC中,我们可以通过RTCCameraVideoCapture类获取所有的视频设备。如下所示:
NSArray<AVCaptureDevice*>* devices = [RTCCameraVideoCapture captureDevices];
AVCaptureDevice* device = devices[0];
通过上面两行代码,我们就拿到了视频设备中的第一个设备。当然,光有设备还不行。我们还要清楚从设备中采集的数据放到哪里了,这样我们才能将其展示出来。WebRTC 为我们提供了一个专门的类,即 RTCVideoSource , 它有两层含义:
视频源。当我们要展示视频的时候,就从这里获取数据;RTCVideoSource* videoSource = [factory videoSource];
除此之外,为了能更方便的控制视频设备,WebRTC 提供了一个专门用于操作设备的类,即 RTCCameraVideoCapture。通过它,我们就可以自如的控制视频设备了。
RTCVideoSource* videoSource = [factory videoSource];
capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:videoSource];
[capture startCaptureWithDevice:device
format:format
fps:fps];
现在已经可以通过RTCCameraVideoCapture类控制视频设备来采集视频了, 那如何获取采集的视频流呢 ? 上面的代码我们已经将视频采集到视频源RTCVideoSource了, 那RTCVideoSource就是我们的视频流吗 ?显然不是。 这里要提到的是WebRTC三大对象中的其中一个对象RTCMediaStream ,它才是我们说的视频流。那它和RTCVideoSource之间是什么关系呢,之间是如何建立关联的呢?
//创建本地流
_localStream = [_factory mediaStreamWithStreamId:@"ARDAMS"];
//获取数据源
_localVideoSource = [_factory videoSource];
//音频
RTCAudioTrack * audioTrack = [_factory audioTrackWithTrackId:@"ARDAMSa0"];
//视频
RTCVideoTrack *videoTrack = [_factory videoTrackWithSource:_localVideoSource trackId:@"ARDAMSv0"];
//将audioTrack、videoTrack添加到流
[_localStream addAudioTrack:audioTrack];
[_localStream addVideoTrack:videoTrack];
//拿到capture对象
RTCCameraVideoCapturer * capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:_localVideoSource];
原来是通过一个中间对象RTCVideoTrack 建立的关联。
RTCCameraVideoCapturer将采集的视频数据交给RTCVideoSource- 通过
RTCVideoSource创建RTCVideoTrackRTCMediaStream添加视频轨 videoTrack。
获取本地流完整的代码如下:
if (!_localStream) {
NSArray<AVCaptureDevice *> *captureDevices = [RTCCameraVideoCapturer captureDevices];
AVCaptureDevice * device = captureDevices[0];
//检测摄像头权限
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
{
NSLog(@"相机访问受限");
//TODO:
if ([self.delegate respondsToSelector:@selector(webRTCClient:setLocalStream:)]) {
[self.delegate webRTCClient:self setLocalStream:nil];
}
} else {
if (device) {
//创建本地流
_localStream = [_factory mediaStreamWithStreamId:@"ARDAMS"];
//获取数据源
_localVideoSource = [_factory videoSource];
//音频
RTCAudioTrack * audioTrack = [_factory audioTrackWithTrackId:@"ARDAMSa0"];
//视频
RTCVideoTrack *videoTrack = [_factory videoTrackWithSource:_localVideoSource trackId:@"ARDAMSv0"];
//将audioTrack、videoTrack添加到流
[_localStream addAudioTrack:audioTrack];
[_localStream addVideoTrack:videoTrack];
//拿到capture对象
RTCCameraVideoCapturer * capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:_localVideoSource];
//format , fps
AVCaptureDeviceFormat * format = [[RTCCameraVideoCapturer supportedFormatsForDevice:device] lastObject];
CGFloat fps = [[format videoSupportedFrameRateRanges] firstObject].maxFrameRate;
//开始采集
_videoCapture = capture;
[capture startCaptureWithDevice:device format:format fps:fps completionHandler:^(NSError * error) {
NSLog(@"startCaptureWithDevice---:%@",error);
dispatch_async(dispatch_get_main_queue(), ^{
//展示预览
if ([self.delegate respondsToSelector:@selector(webRTCClient:setLocalStream:)]) {
[self.delegate webRTCClient:self setLocalStream:self.localStream];
}
});
}];
}
else
{
NSLog(@"该设备不能打开摄像头");
if ([self.delegate respondsToSelector:@selector(webRTCClient:setLocalStream:)]) {
[self.delegate webRTCClient:self setLocalStream:nil];
}
} //end device
}//end auth
}
RTCPeerConnection是WebRTC用于构建点对点之间稳定、高效的流传输的组件,是WebRTC三大核心组件之一。 使用它我们可以建立一条与远端通话的音视频数据传输通道。
上面提到了PeerConnection工厂 RTCPeerConnectionFactory, RTCPeerConnection 的实例就是通过此工厂来创建.
if (!ICEServers) {
ICEServers = [NSMutableArray array];
[ICEServers addObject:[self defaultSTUNServer]];
}
RTCConfiguration* configuration = [[RTCConfiguration alloc] init];
[configuration setIceServers:ICEServers];
RTCPeerConnection* conn = [factory
peerConnectionWithConfiguration:configuration
constraints:[self defaultPeerConnContraints]
delegate:self];
- (RTCMediaConstraints *)defaultPeerConnContraints
{
RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@{kRTCMediaConstraintsOfferToReceiveAudio:kRTCMediaConstraintsValueTrue,kRTCMediaConstraintsOfferToReceiveVideo:kRTCMediaConstraintsValueTrue} optionalConstraints:nil];
return constraints;
}
对于 iOS 的 RTCPeerConnection 对象有三个参数:
RTCConfiguration 类型的对象,该对象中最重要的一个字段是 iceservers。它里边存放了 stun/turn服务器地址。其主要作用是用于NAT穿越。RTCMediaConstraints 类型对象,也就是对 RTCPeerConnection 的限制。如,是否接收视频数据?是否接收音频数据?如果要与浏览器互通还要开启 DtlsSrtpKeyAgreement 选项。委托类型。相当于给 RTCPeerConnection 设置一个观察者。这样RTCPeerConnection 可以将一个状态/信息通过它通知给观察者。但它并不属于观察者模式,这一点大家一定要清楚。详见: https://zhanglei.blog.csdn.net/article/details/122539459
我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上
我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我