WebRTC封装静态库
2023-6-6
| 2023-8-10
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password

📥WebRTC封装桌面采集功能

 

简介

本文用于记录在Win11环境下,封装WebRTC-Native-M109版本的动态库调用库的过程。

环境

  • win11
  • WebRTC M109

正文

本次编译工作是基于成功使用WebRTC推流桌面到SRS服务器的Demo,将屏幕共享功能独立抽离出来,也为以后动态编译其他模块的工作打个底。
话不多说

Step1. 编译通过封装项目

Step2.封装库需要注意的点

  • C/C++ 不同封装的区别 [[深入静态库、动态库]]
  • 几条代码之间的区别 函数调用方式 __stdcall__cdecl 参考 [[函数调用方式 __stdcall跟__cdecl详解]] 这里得到的结论之一是如果你的程序中没有涉及可变参数,最好使用__stdcall

遇到的问题以及思考

编译过程中,有一个问题困扰了我一段时间,就是一个网络类的信号槽无法响应。具体情况是这样,在Qt的主线程中有一个调用类A,它负责调用SDK中封装的C风格函数,我在A的一个方法中使用lambda调用SDK函数创建了一个Controller,然后在A->First 方法中调用SDK::CreateDomain,并把A::FirstCallback作为回调传入了CreateDomain中,并且SDK把回调存储起来,在经过一段时间后,SDK::GetMessage获得了讯息,开始调用A::FirstCallback,我在FirstCallback中使用了网络请求,但是槽函数始终无法响应,但是把它单独拎出来发放在主线程执行也可以得到响应。做让我们来梳理一下。见下列代码。
 
main.h
这里使用了一个技巧 ,因为在库函数中定义的SDK::SetCallback的回调函数 是纯函数指针,把A的普通成员函数作为回调是不行的,因为不存在 从类的成员函数的指针直接转化成函数指针的隐式转换,所以这里我们面临有几个选择 1: 手搓一个转化方法,让这个类成员函数指针转化成对应的函数指针,这很麻烦,而且不一定能实现,我放弃。 2:使用static 修饰另一个函数,当然,使用static修饰之后 ,这个函数就不属于这个类了,具体细节参考static深入理解,然后在这个static函数中调用原本的想要调用的A::FirstCallback,这相当于一次桥接。这样实现也不是很优雅,函数调来调去看上去比较凌乱。暂时放弃。 3:使用喜闻乐见的lambda委托一下,跟第二种其实很类似,但是lambda可以直接被传入当作参数,更方便与高效,笔者正是使用的这个方式。
class_a.h
那么从现在开始,函数执行栈执行到了SDK::SetCallBack,我们知道,被调用的函数运行结束后当前帧全部收缩,回到调用者的帧,而在这里,我们使用的是匿名函数,他其实在传入的时候是作为闭包对象,并且分配在堆上,并没有传入函数调用栈,栈上只保存了必要的帧信息。 ![[Pasted image 20230601103935.png]]
HttpManager 是一个继承自QObject的单例类 http_manager.h
sdk.h
重点来了,此时,我们执行到了SDK::SetCallback,而SetCallback中给固定的一个session对象设置了回调函数,那么此时,通过闭包就已经传递到了sdk库内的session对象中,然后在SetCallback中通过特定的事件机制,通过线程异步延迟在OnCall得到了响应,此时OnCall是在SDK函数内部的子线程内,因此,在OnCall内部调用的ses_->mfirst_callback((void*)_ses_,message.c_str(),ses->mfirst_callback_usr_); 自然也是在子线程的函数调用栈上!,线程有自己独立的函数调用栈,理解这一点尤为重要。那么回到调用,在这个子线程中,回到了A的函数A::FirstCallback上,我们再回顾一下这个函数,
此时一切正常,顺利走入HttpManager->postRequest()中,并传递了一个闭包给HttpManager作为回调保存下来。 但是,所有的消息在m_accessManager Post请求出去之后石沉大海,没有响应。
一开始笔者排除了几个方向
  • HttpManager的构造函数接受了调用者作为父类对象,而它的成员对象 m_accessManager把调用者作为父类对象,因此执行Post请求跟需要接受的槽函数类不在同一个线程,所以只要将Connect申明成跨线程调用就可以了? 但是证实无效
  • 使用笔者总结的另外一套常用的网络模型,核心方法是moveToThread,通过Controller来控制内部线程的一切I/O请求,所有参数通过信号槽传递。 这套方法可以是可以,但是信息部分都是通过信号槽来传递的,我有意的在新的网络模型中寻求更高效的方式,所以这套作为备用。
  • 其他的方法都太过于黑魔法,确实可行,但是不适合在项目工程中使用,比较破坏编程规范,这里也不再赘述。 还有两个方向,手搓事件类使用Qt自带的跨线程异步回调相关的类 QEventLoop或者QInvoke,写代码的时候不宜尝试过多,项目中能用且可控是第一要义,需要快速产出,所以使用了QEventLoop。把FirstCallback内部包起来,形成一个loop,lambda闭包回来的时候因为loop没有退出,所以m_accessManager 的槽函数能响应到。这样确实可行,但是有个问题,eventloop会阻塞主线程,也就是Ui线程,但是这里使用起来确实是没问题的,渲染会在另外的线程中执行,这里只是不能退出。

一点思考:

所以前面的问题,大概方向是,QNetworkAccessManager 对象所在的线程与接收信号的对象不在同一个线程,可能会导致信号无法正确触发。但是其实都是基于QObject,connect函数设置成异步调用,为什么不能触发这一点还没有特别清楚。还有它的生命周期应该是全局的,但是生效条件比较苛刻,这一点可能跟SDK库中的环境也有关。

写在最后

  • 代码中需要有I/O对象的时候,需要特别关注对象的生命周期
  • 对于回调函数需要谨慎使用,使用者应该明确内部对象的生命周期,确实很容易出现线上问题。
  • 学无止境~
技术分享
  • WebRTC
  • 开发
  • WebRTC Native添加H264+OpenSSL支持SDP协议理解
    • Twikoo
    • Giscus
    目录