目录
1、概述
2、开源跨平台多媒体库SDL介绍
3、开源音视频实时通信库WebRTC介绍
4、在国产化Linux桌面系统中遇到的SDL多线程问题
5、在给WebRTC新增外部音频插件库时遇到的多线程问题
6、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/category_12279968.html 在音视频项目中使用到了开源跨平台多媒体库SDL和开源音视频实时通信库WebRTC,在使用过程中遇到了一类多线程问题,依据项目问题的实战排查过程,对这类问题进行一个总结,给大家提供一个借鉴或参考。
1、概述
开源跨平台多媒体库SDL,因为其良好的跨平台特性和功能封装,被广泛地应用于游戏开发、模拟器、媒体播放器、视频会议等多个应用领域中。
开源音视频实时通信库WebRTC因为其较好的音视频效果及良好的网络适应性,目前已被广泛的应用到视频会议、实时音视频直播等领域中。在视频会议领域,腾讯会议、华为WeLink、字节飞书、阿里钉钉、小鱼易连均提供了基于WebRTC方案的视频会议。
使用这些开源库的应用程序运行在各式各样的系统环境中,不可避免地会出现这样或那样的问题。本文将详细讲述使用SDL和WebRTC过程遇到的两例典型的线程约束问题。
2、开源跨平台多媒体库SDL介绍
SDL(Simple DirectMedia Layer)是一套开源的跨平台多媒体开发库,使用 C 语言写成。它提供了绘制图像、播放声音、获取键盘输入等相关的 API,大大降低多媒体应用开发难度的同时,也让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。多用于开发游戏、模拟器、媒体播放器、音视频播放等多个应用领域。
SDL 有两个常见版本:SDL1.2 和 SDL2.x。在不支持 OpenGL ES2 的嵌入式平台上,只能使用 SDL1.2,SDL2.x 依赖 OpengGL ES2。
在音视频开发领域,主要使用SDL去实现音视频的播放。SDL底层根据不同平台采用不同的渲染技术,比如在Windows平台上使用DirectX去渲染,在Linux平台上则依赖OpenGL去显示。
在Windows平台上我们可以去操作Windows平台专用的DirectX库中D3D9或D3D11去绘制视频图像,在Linux国产化桌面系统中则使用SDL库去进行视频图像的渲染与绘制(Linux平台上SDL底层使用的是OpenGL)。
3、开源音视频实时通信库WebRTC介绍
WebRTC(Web Real-Time Communication)是一个由Google发起的实时音视频通讯C++开源库,其提供了音视频采集、编码、网络传输,解码显示等一整套音视频解决方案,我们可以通过该开源库快速地构建出一个音视频通讯应用。
一个实时音视频应用软件一般都会包括这样几个环节:音视频采集、音视频编码(压缩)、前后处理(美颜、滤镜、回声消除、噪声抑制等)、网络传输、解码渲染(音视频播放)等。每一个细分环节,还有更细分的技术模块。
WebRTC的起源,要从2010年Google以6820万美元收购VoIP软件开发商Global IP Solutions的 GIPS引擎谈起,在经过收购之后没多久,Google将该引擎改名为“WebRTC”,并宣布向全球开发者开源。
WebRTC项目最开始是让Web开发者能够基于浏览器(Chrome\FireFox\...)轻易快捷开发出丰富的实时音视频应用,而无需下载安装任何插件,Web开发者也无需关注音视频的处理过程,只需编写简单的Javascript程序即可实现。WebRTC库底层是用C/C++实现的,具有良好的跨平台性能,当前已提供对Windows、MAC、iOS和Andriod等多系统的支持,我们只需要调用对应系统的SDK即可完成这些系统上音视频应用的构建。
虽然其名为WebRTC,但是实际上它不光支持Web之间的音视频通讯,还支持Windows、Android以及iOS等移动平台。WebRTC底层是用C/C++开发的,具有良好的跨平台性能。
WebRTC因为其较好的音视频效果及良好的网络适应性,目前已被广泛的应用到视频会议、实时音视频直播等领域中。在视频会议领域,腾讯会议、华为WeLink、字节飞书、阿里钉钉、科达、ZOOM、小鱼易连均提供了基于WebRTC方案的视频会议。
大家熟知的音视频专业服务商声网(Agora),更是基于开源WebRTC库,提供了社交直播、教育、游戏电竞、IoT、AR/VR、金融、保险、医疗、企业协作等多个行业的音视频互动解决方案。使用声网服务的企业包括小米、陌陌、斗鱼、哔哩哔哩、新东方、小红书、HTC VIVE 、The Meet Group、Bunch、Yalla等遍布全球的巨头、独角兽及创业企业。
除了头部公司声网之外,也陆续有多家公司基于开源的WebRTC,开发出了多个音视频应用,提供了多个领域的音视频通信解决方案。
4、在国产化Linux桌面系统中遇到的SDL多线程问题
在国产化客户端应用程序中我们选择使用SDL库实现音视频的播放,这个播放方案在多个国产化系统都有过验证,是可行的。比如在统信UOS系统、银河麒麟、中标麒麟等国产化操作系统中均进行了详细的测试,音视频均能正常且流畅的播放。
但在某个项目中确遇到了问题,在客户环境中,其他电脑上客户端软件视频播放都是正常的,唯独在某台电脑上始终有问题,视频无法显示出来,一直显示黑屏。这个有点奇怪,其他国产化电脑上都没问题,唯独这台电脑上有问题。
相对于服务器程序,客户端程序在环境兼容方面有很大的压力和不确定性。服务器硬件设备和操作系统一般都是软件系统提供商提供并固定下来的,不会有很大的变动,服务器程序一般只要在指定的服务器软件环境中充分测试就可以了,们不用考虑对不同软硬件的兼容性问题。
客户端程序则有很大的不同,客户端程序要运行在客户的电脑上,要尽量兼容各式各样电脑的软硬件环境。这里讲的软件,主要是指不同版本的操作系统;硬件则是指电脑上的硬件芯片(比如不通厂商的显卡、USB外接摄像头)及芯片驱动程序。在客户端程序测试过程中,不可能覆盖所有的软硬件环境,所以当产品发布给客户使用后,经常会出现这样或那样的问题。这类兼容性问题,我们遇到过很多次,使用各种办法去处理或规避这些问题。
为了排查这个问题,我们在调用SDL库接口实现视频播放的代码中添加了大量的打印,重新编译版本,拿到客户出问题的机器上运行。但根据运行日志始终查不出问题。
在国产化Linux平台上SDL底层使用的是系统OpenGL组件实现视频渲染的,是不是这台问题电脑上的OpenGL组件版本比较低,对多线程的支持不太好。在软件中创建SDL对象的代码和绘制视频图像的代码分别运行在不同的线程中,是不是要挪到同一个线程中呢?
于是说干就干,动手编写了一个demo程序,将创建SDL对象的代码和绘制视频图像的代码都挪到一个线程中,然后拿到客户的问题电脑上测试,果然这个方法是奏效的,视频如期地显示了出来。于是将修改后的库放到我们的客户端软件中,视频图像果然显示出来了!这个问题折腾的比较久,至此,这个问题总算是解决了!
5、在给WebRTC新增外部音频插件库时遇到的多线程问题
WebRTC支持以外接插件库的方式添加对音视频额外处理,我们在项目为了解决音频问题,我们额外编写了一个对音频数据做特殊处理的库,以插件的方式接入到WebRTC库中。但添加这个外接插件模块后,遇到了一个很奇怪的问题,Release版本的程序运行是没问题的,但Debug版本程序刚启动就会发生闪退,这个问题是必现的。使用Visual Studio在Debug下发起调试时,程序刚启动就闪退了,Visual Studio看不到有效的函数调用堆栈。遇到这种情况,我们该怎么办呢?我们说过多次了,遇到这种情况,我们直接使用Windbg动态调试就好了。
因为程序闪退发生在启动时,所以我们选择使用Windbg去启动Debug版本程序。刚启动起来就产生了异常中断,是调用了abort函数引发的中断,查看此时的函数调用堆栈。
以前我们讲过,调用abort函数会让正在调试的调试器Windbg中断下来。因为abort函数内部会raise(产生)一个SIGABRT信号终止异常,如果当前正在调试状态,会让调试器中断下来。abort函数的内部实现源码如下所示:
/*** *void abort() - abort the current program by raising SIGABRT * *Purpose: * print out an abort message and raise the SIGABRT signal. If the user * hasn't defined an abort handler routine, terminate the program * with exit status of 3 without cleaning up. * * Multi-thread version does not raise SIGABRT -- this isn't supported * under multi-thread. *******************************************************************************/ void __cdecl abort ( void ) { _PHNDLR sigabrt_act = SIG_DFL; #ifdef _DEBUG if (__abort_behavior & _WRITE_ABORT_MSG) { /* write the abort message */ _NMSG_WRITE(_RT_ABORT); } #endif /* _DEBUG */ /* Check if the user installed a handler for SIGABRT. * We need to read the user handler atomically in the case * another thread is aborting while we change the signal * handler. */ sigabrt_act = __get_sigabrt(); if (sigabrt_act != SIG_DFL) { raise(SIGABRT); } /* If there is no user handler for SIGABRT or if the user * handler returns, then exit from the program anyway */ if (__abort_behavior & _CALL_REPORTFAULT) { _call_reportfault(_CRT_DEBUGGER_ABORT, STATUS_FATAL_APP_EXIT, EXCEPTION_NONCONTINUABLE); } /* If we don't want to call ReportFault, then we call _exit(3), which is the * same as invoking the default handler for SIGABRT */ _exit(3); }
根据调用堆栈中显示的模块信息取来了对应的pdb符号库路径,然后查看详细的函数调用堆栈,然后将函数调用堆栈与WebRTC开源库的源码对照起来看,找到了触发abort函数调用的原因。是因为WebRTC开源库内部判断出创建音频设备管理对象的线程与调用RegisterAudioCallback接口设置音频回调的操作所在的线程不在同一个线程,RTC_DCHECK_RUN_ON宏内部会判断是不是同一个线程,然后进入RTC_DCHECK宏,这个RTC_DCHECK宏内部检测到不在同一个线程中,就会调用Fatal_Message接口,然后Fatal_Message接口中又调用了FatalLog接口,FatalLog接口中最终调用了abort函数,将进程强行终止掉了,相关代码截图如下:
这个RTC_DCHECK宏只在Debug下有效,所以Debug下触发了abort函数调用,Release下不起作用,所以Debug下会闪退,而Release不会闪退!
由上可知,WebRTC内部要求创建音频设备管理对象的操作与调用RegisterAudioCallback接口设置音频回调的操作必须在同一个线程中,所以问题的解决办法就是,在上层业务模块中将这两个操作放到一个线程中即可。
使用RegisterAudioCallback方法实现音频处理时,需要遵循一定的线程安全性原则,确保音频数据的采集和处理不会造成多线程竞争条件和死锁等问题。多线程竞争条件解释如下:
多线程竞争条件是指多个线程在同时访问共享资源时,由于访问时序不确定或者操作不完整,导致结果出现错误或者不确定的情况。竞争条件是多线程编程中常见的一种问题,如果处理不当,可能会导致程序出现各种问题,如崩溃、死锁、数据损坏等。
竞争条件的解决方案包括:
锁机制:使用互斥锁(Mutex)、读写锁(ReadWriteLock)等同步机制,保证每个线程对共享资源的访问是互斥的,防止出现竞争条件。
原子操作:使用原子操作(Atomic Operation)可以保证对共享资源的访问是原子性的,即不会被中断,保证了访问的完整性和一致性。
条件变量:使用条件变量(Condition Variable)可以实现线程之间的通信和协调,避免出现死锁和饥饿等问题。
信号量:使用信号量(Semaphore)可以限制并发线程的数量,防止过多的线程同时访问共享资源。
总之,多线程编程中需要注意竞争条件问题,保证线程安全和程序正确性。合理地使用锁机制、原子操作、条件变量和信号量等同步机制,可以有效地避免竞争条件问题的出现。
6、最后
上面两个使用开源库时遇到的线程约束问题,是我们在实际项目中遇到的,之前没碰到过这类线程约束问题。今天在这里做个详细的总结,给大家提供一个借鉴或参考。