qt语音现在还有吗,qt平台插件windows
简介WebRTC在之前的文章中已经讲解过,所以我认为不需要详细介绍。在之前的文章中,我们在Web端和Android端实现了一个音视频通话项目,而这次我们将使用QT UI框架在Windows端构建一个实用的多P2P音视频通话项目。项目地址:github.com/yangkun1992.Android和Web上运行后最终效果为:
设置环境1. 准备QT环境的首选是下载最新版本的QT 6.6.0。下载地址为: www.qt.io/download。安装完成后,选择cmake构建项目。
2. 准备信令服务器2.1 克隆信令服务器代码
git clone https://github.com/yangkun19921001/OpenRTCProject.git2.2 启动信令服务器
cd p2ps/server//更改自己的证书const server=process.env.HTTPS==='true' http.createServer({ key : fs.readFileSync('./cert/rtcmedia.top.key') , cert: fs .readFileSync('./cert/rtcmedia.top_bundle.pem') }, app): http.createServer(app);//执行启动命令。 /server_run.js./proxy_server_run.js//检查是否已经启动。 lsof -i:8880和4433成功准备webrtc静态库clone webrtc(m98)开发代码git clone https://github.com/yangkun19921001/OpenRTCClient.git根据README编译,编译完成后创建静态库build/win/get debug/obj/webrtc.lib4 .根据官方文档准备编译Socketio-client-cpp 2.0.0静态库。请参阅:github.com/socketio/so. 5.准备QT cmake。完成上述四个步骤后,准备就绪后就可以使用cmake 进行编译了。依赖关系如下。
cmake_minimum_required(版本3.5)项目(p2ps版本0.1语言CXX)设置(CMAKE_AUTOUIC ON)设置(CMAKE_AUTOMOC ON)设置(CMAKE_AUTORCC ON)设置(CMAKE_CXX_STANDARD 17)设置(CMAKE_CXX_STANDARD_REQUIRED ON)设置(BUILD_TYPE调试)if(MSVC)if(CMAKE_ CMAKE ) _CXX_FLAG S_RELEASE} /MT') 设置(CMAKE_C_FLAGS_RELEASE) '$ {CMAKE_C_FLAGS_RELEASE} /MT') #set(BUILD_TYPE 版本) endif()endif()set(WEBRTC_THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/././. /webrtc/third_party)设置(LIBWEBRTC_INCLUDE_PATH ${ CMAKE_CURRENT_SOURCE_DIR}/. /././webrtc)设置(LIBWEBRTC_BINARY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/./././build_system/build/win/x64/$ {BUILD_TYPE}/obj)set(DEPS_ROOT_PATH $ {CMAKE_CURRENT_SOURCE_DIR} /deps )set(SOCKET_IO_BINARY_PATH ${DEPS_ROOT_PATH}/socketio/win/x64/${BUILD_TYPE})set(SOCKET_IO_INCLUDE_PATH ${DEPS_ROOT_PATH}/socketio/include)set(JSONCPP_SOURCE '${WEBRTC_THIRD_PARTY _PATH}/jsoncpp/source/src /lib_json /json_reader .cpp' '${WEBRTC_THIRD_PARTY_PATH}/jsoncpp/source/src/lib_json/json_tool.h' '${WEBRTC_THIRD_PARTY_PATH}/jsoncpp/source/src/lib_json /json_value.cpp' '${WEBRTC_THIRD_PARTY_PATH}/jsoncpp/source /src /lib_json/json_writer.cpp')target_include_directories(${PROJECT_NAME} PUBLIC '${LIBWEBRTC_INCLUDE_PATH}' '${LIBWEBRTC_INCLUDE_PATH}/third_party/abseil-cpp' ' ${LIBWEBRTC_INCLUDE_PATH}/third_party/jsoncpp/source/include' ' ${ LIBWEBRTC_INCLUDE_PATH}/third_party/jsoncpp/generated' '${LIBWEBRTC_INCLUDE_PATH}/third_party/libyuv/include' '${SOCKET_IO_INCLUDE_PATH}')add_settings( #webrtc qt crash -DQT_DEPRECATED_WARNINGS -DQT_NO_KEYWORDS #jsoncpp - DJSON_USE_EXCEPTION=0 -DJSON_USE_NULLREF=0 #socketio #-DSIO_TLS -DUSE_AURA=1 -D_HAS_EXCEPTIONS=0 -D__STD_C -D_CRT_RAND_S -D_CRT_SECURE_NO_DEPRECATE -D_SCL_SECURE _NO_DEPRECATE -D_ATL_NO_OPENGL -D_WINDOWS -DCERT_CHAIN_PARA_HAS_ EXTRA_FIELDS -DPSAPI _VERSION=2 - DWIN32 - D_SECURE_ATL -DWINUWP -D__WRL_NO_DEFAULT_LIB__ #-DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP - DWIN10=_WIN32_WINNT_WIN10 - DWIN32_LEAN_AND_MEAN -DNOMINMAX -D_UNICODE -DUNICODE -DNTDDI_VERSION=NTDDI_WIN10_RS2 -D_WIN32_WINNT=0x 0A00 -DWINVER=0x0A00 -DDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -DWEBRTC_ENABLE_PROTOBUF=0 -DWEBRTC_INCLUDE _INTERNAL_AUDIO_DEVICE - DRTC_ENABLE_VP9 -DHAVE_SCTP -DWEBRTC_LIBRARY_IMPL -DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0 -DWEBRTC_WIN -DABSL_ALLOCATOR_NOTHROW=1 - DHAVE_SCTP -DWEBRTC_VIDEO_CAPTURE_WINRT ) target_link_libraries(p2ps 私有. ${SOCKET_IO_BINARY_路径}/sioclient.lib ${SOCKET_IO_BINARY_PATH}/sioclient_tls.lib ${LIBWEBRTC_BINARY_PATH}/webrtc。 lib winmm.lib iphlpapi.lib wbemuuid.lib secure 32.lib advapi32.lib Mmdevapi.lib Mfuuid.lib msdmo.lib dmoguids.lib wmcodecdspuuid.lib comdlg32.lib dbghelp.lib dnsapi.lib gdi32.lib msimg32.lib odbc32.lib odbccp32.lib oleaut32.lib shell32.lib shlwapi.lib user32.lib usp10。 lib uuid.lib version.lib wininet.lib winmm.libwinspool.lib ws2_32.lib Lateimp.lib kernel32.lib ole32.lib crypt32.lib amstrmid.lib strmiids.lib 至此,QT项目的WebRTC和SocketIO环境为设置好了。但是为什么这里需要构建SocketIO呢?之前的web、服务器、Android都使用了Socketio开源库进行信令通信,正好是跨平台的,所以替换掉也是个好主意。我可以't。
如何构建多人音视频通话要在QT中构建多人音视频通话项目,需要执行以下步骤。我们推荐相关的学习资料。点击下面的链接免费注册。先码,别犹豫~】免费音视频学习地址:https://xxetb.xet.tech/s/2cGd0 【免费分享】音视频学习资料包、领先厂商面试题、技术视频、学习路线图(C/C++、Linux、FFmpeg webRTC rtmp hls rtsp ffplay srs等)需要的话点击788280672进群免费领取~
1、信令交互协议的服务器端信令其实设计得非常简单,只有三组信令,如下图。房间和成功退出消息:交换offer和候选人的详细流程请参考下面的流程图。
2. 为了封装本地信令交互,首先定义ISinnalClient.h 抽象接口。
命名空间PCS{class ISignalClient {public: enum class SignalEvent { JOINED=0, JOIN, LEAVED, LEAVE, MESSAGE }; static std:string SignalEventToString(SignalEvent event) { switch(event) { case SignalEvent:JOINED: return 'joined'; case Signal event :LEAVED :返回' left' ; case SignalEvent:JOIN: return 'join'; case SignalEvent:LEAVE: return 'leave'; case SignalEvent:MESSAGE: return 'message'; default: return 'unknown'; } } /*连接到服务器*/virtual bool connect(const std :字符串URL,OnSignalEventListener * Listener)=0; /*加入会话*/virtual void join(const std:string roomId)=0; /*终止会话*/virtual void Leave(const std:string roomId)=0; /*销毁客户端*/virtual void release( )=0。只需将实现封装在相应的API中即可,详细使用方法请参考github.com/yangkun1992.
3. Multi-PeerConnection管理Multi-PeerConnection管理实际上就是将每次加入一个房间的Peer添加到一个容器中并对其进行管理。您可以在此处定义管理的地图结构。核心API 是: 3.1 定义PeerConnection管理结构
struct Peer{ Peer() {} rtc:scoped_refptrpeer_conn_inter_; std:unique_ptrpeer_conn_obser_impl_; std:unique_ptr create_offer_sess_des_impl_; std:unique_ptr create_answer_sess_ des _impl_ ;};这里主要包含了每个PeerConnection所需的成员,并通过std:map进行管理,如下所示。
std:mappeers_;map 的键是连接到房间的id3.2。创建PeerConnectionFactory 您可以通过以下核心代码创建PeerConnectionFactory。
peer_connection_factory_=webrtc:CreatePeerConnectionFactory( this-network_thread_.get() /* 网络线程*/, this-worker_thread_.get() /* 工作线程*/, this-signaling_thread_.get(), /* 信号发送线程*/nullptr /* default_adm */, webrtc:CreateBuiltinAudioEncoderFactory(), webrtc:CreateBuiltinAudioDecoderFactory(), webrtc:CreateBuiltinVideoEncoderFactory(), webrtc:CreateBuiltinVideoDecoderFactory(), null pt r /* audio_mixer */, nullptr /* audio_processing */); 创建成功后,创建本地音视频轨道,然后创建音视频轨道添加到PeerConnection
//音频轨道audio_track_=peer_connection_factory_-CreateAudioTrack( kAudioLabel,peer_connection_factory_-CreateAudioSource( cricket:AudioOptions())); //视频轨道rtc:scoped_refptr video_device=CameraCapturerTrackSource:Create( 1280, 7 20, 30); video_track_=peer_connection_factory_-CreateVideoTrack(kVideoLabel, video_device); 3.3 创建对等连接
bool PeerManager:createPeerConnection(const std:string 对等ID, const webrtc:PeerConnectionInterface:RTCConfiguration config , OnPeerManagerEvents* ets){ RTC_LOG(LS_INFO) __FUNCTION__ ' 对等ID : ' Peer_ con n_obimpl=std:make_unique(peerId,ets); 自动对等连接=peer_connection_factory_-CreatePeerConnection ( config, nullptr, nullptr ,peer_conn_obimpl.get( )); if (peerConnection) { autopeer_ptr=std:make_unique();peer_ptr-peer_conn_inter_=peerConnection;peer_ptr-peer_conn_obser_impl_=std:move(peer_conn_obimpl);pe ers_[peerId]=std3 3360:mo ve(peer _ptr ); 从上面代码中,通过peer_connection_factory_-CreatePeerConnection我们构造了一个PeerConnection,将之前创建的本地音视频轨道添加到该PeerConnection中,最后我们将构造好的PeConn缓存在map中,方便进一步处理。 3.4 创建报价
void PeerManager:createOffer(const std:stringpeerId, OnPeerManagerEvents* ets){ RTC_LOG(LS_INFO) __FUNCTION__ 'peerId:'second-peer_conn_inter_ !=nullptr) { auto obs=std:make_unique(tru) e,pe erId ,ets ); it-second-peer_conn_inter_-CreateOffer( obs .get(), webrtc:PeerConnectionInterface:RTCOfferAnswerOptions()); it-second-create_offer_sess_des_impl_=std:move(obs); }else { RTC_LOG(LS_ERROR) __FUNCTION__ 'peers_ not found id:'在上述代码中,首先根据从缓存中获取对应的PeerConnection peerId 要做的事。接下来,调用createOffer3.5内部API来配置本地/远程SDP。
void PeerManager:setLocalDescription(const std:stringpeerId,webrtc:SessionDescriptionInterface *desc_ptr){RTC_LOG(LS_INFO)__FUNCTION__'peerId:'第二个peer_conn_inter_-SetLocalDescription(SetSessionDescriptionOb serverImpl3)336033 360Create(),desc_ptr); }else { RTC_LOG(LS_ERROR) __FUNCTION__ ' Peer_ not found id: ' 秒-peer_conn_inter_ -SetRemoteDescription(SetSessionDescriptionObserverImpl:Create(),desc_ptr); }else { RTC_LOG(LS_ERROR) __FUNCTION__ 'peers_ not find id:' 从缓存中获取对应的PeerConnection,并设置本地或远程SDP 描述信息。
void PeerManager:createAnswer(const std:stringpeerId, OnPeerManagerEvents* ets){ RTC_LOG(LS_INFO) __FUNCTION__ 'peerId:'second-peer_conn_inter_ !=nullptr) { auto obs=std:make_unique( false, 对等ID ,ets); it -第二个peer_conn_inter_-CreateAnswer(obs) .get(), webrtc:PeerConnectionInterface:RTCOfferAnswerOptions()); it-second-create_answer_sess_des_impl_=std:move(obs); }else { RTC_LOG(LS_ERROR) __FUNCTION__ 'peers_ not found id:'这与上一步的逻辑相同3.7中的冰处理是
void PeerManager:handleCandidate(std:string 对等ID,std:unique_ptr 候选者){ RTC_LOG(LS_
INFO) << __FUNCTION__ <<" peerId:"<second->peer_conn_inter_->AddIceCandidate(std::move(candidate),[peerId](webrtc::RTCError error){ if (error.ok()) { RTC_LOG(LS_INFO) <<" peerId:"<< peerId << " AddIceCandidate success."; } else { RTC_LOG(LS_INFO) <<" peerId:"<< peerId << "AddIceCandidate failed, error: " << error.message(); }}); }else { RTC_LOG(LS_ERROR) << __FUNCTION__ << " peers_ not found id:"<也是从缓存中拿到对应的 PeerConnection ,然后将对方的候选者地址添加进去。 4. 房间管理定义 RTCRoomManager ,实现信令回调和PeerManager 回调,定义的核心 API 如下 namespace PCS{class RTCRoomManager : public OnSignalEventListener,public OnPeerManagerEvents {public: RTCRoomManager(); virtual ~RTCRoomManager(); //连接服务器 void connect(const std::string url,OnRoomStateChangeCallback* callback); //设置本地轨道的回调监听 void setLocalTrackCallback(std::function)> localVideoTrack); //加入房间 void join(const std::string roomId); //离开房间 void leave(const std::string roomId); //销毁 void release(); //处理 ui 传递过来的 消息 void onUIMessage(Message msg);private: //实现连接成功的处理代码 void onConnectSuccessful() override; //实现正在连接的处理代码 void onConnecting() override ; //实现连接错误的处理代码 void onConnectError(const std::string& error) override ; //实现已加入房间的处理代码 void onJoined(const std::string& room, const std::string& id, const std::vector& otherClientIds) override; //实现离开房间的处理代码 void onLeaved(const std::string& room, const std::string& id) override ; //实现接收到消息的处理代码 void onMessage(const std::string& from, const std::string& to, const std::string& message) override ; //当需要添加远端的轨道 void OnAddTrack(std::string peerid,rtc::scoped_refptr receiver,const std::vector>& streams)override; //当删除远端的轨道 void OnRemoveTrack(std::string peerid, rtc::scoped_refptr receiver) override; // datachannel 消息 void OnDataChannel(std::string peerid, rtc::scoped_refptr channel) override; //ice 消息 void OnIceCandidate(std::string peerid,const webrtc::IceCandidateInterface* candidate) override; //offer or answer create 成功 void OnCreateSuccess(bool offer,std::string peerid,webrtc::SessionDescriptionInterface* desc) override; //offer or answer create 失败 void OnCreateFailure(bool offer,std::string peerid,webrtc::RTCError error) override;private: std::unique_ptr socket_signal_client_imp_; std::unique_ptr peer_manager_; OnRoomStateChangeCallback * room_state_change_callback_; std::function)> local_track_callback_; std::string room_id_;};} // end namespace PCS这就是核心 API, 它持有peer_manager_、socket_signal_client_imp_,分别是对 PeerConnection 和信令的交互。比如现在 A 用户先进入房间,B 后进入房间,然后对它们的管理流程是这样的 A 用户连接服务器 -> 连接成功 ->发起 join 信令->收到 joined 信令->createPeerConnectionFactory->摄像头开始采集->等待预览B 用户连接服务器 -> 连接成功 ->发起 join 信令 ->收到joined 信令(并带上了 A 用户在房间中的信息) ->createPeerConnectionFactory->等待本地预览->createPeerConnection(A用户)A 用户收到 B 用户 joined 加入房间的信令 -> createOffer -> 设置本地 SDP -> 发送 本地 SDP offer 信令到服务器B 用户收到 服务器转发过来的 A 用户的 offer 信令 -> 设置远端的 SDP 信息 ->CreateAnswer(A用户)->设置本地 SDP 信息 -> 发送 answer 消息给 AA 收到 B 发送的 answer 信令消息,调用 setRemoteDescription 函数将 B 的 SDP 设置进去A,B 交换 candidate 消息,并将对方的 candidate 调用 AddIceCandidate 添加进去到这一步后,就等待 onAddTrack 回调了,下一步就是如何将对方和本地的画面进行显示了5. 如何显示在 webrtc 架构中,是通过 rtc::VideoSinkInterface 的 onFrame 虚函数进行通知需要新视频数据的渲染。我们定义一个 VideoRendererWidget 然后实现 rtc::VideoSinkInterface 的 onFrame 函数,如下所示: namespace PCS {class VideoRendererWidget : public QOpenGLWidget, protected QOpenGLFunctions,public rtc::VideoSinkInterface{ Q_OBJECTpublic: VideoRendererWidget(std::string peerId ="",QWidget* parent = nullptr,webrtc::VideoTrackInterface *track =nullptr); ~VideoRendererWidget();public Q_SLOTS: void PlayOneFrame();protected: void initializeGL() Q_DECL_OVERRIDE; void resizeGL(int w, int h) Q_DECL_OVERRIDE; void paintGL() Q_DECL_OVERRIDE;public: // VideoSinkInterface implementation void OnFrame(const webrtc::VideoFrame& frame) override;private:...public: webrtc::VideoTrackInterface* video_track_;};}void VideoRendererWidget::OnFrame(const webrtc::VideoFrame &video_frame){ std::lock_guard guard(renderer_mutex_); rtc::scoped_refptr buffer( video_frame.video_frame_buffer()->ToI420()); if (video_frame.rotation() != webrtc::kVideoRotation_0) { buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation()); } int width = buffer->width(); int height = buffer->height(); int ySize = width * height; int uvSize = ySize / 4; // For each U and V component if(m_nVideoW == 0 && m_nVideoH ==0) {if(!peer_id_.empty()) RTC_LOG(LS_INFO) << __FUNCTION__ << " init w:"<(width * height * 1.5); // Use make_unique to allocate array if(!peer_id_.empty()) RTC_LOG(LS_INFO) << __FUNCTION__ << " malloc Id:"<DataY(), ySize); memcpy(video_data_.get() + ySize, buffer->DataU(), uvSize); memcpy(video_data_.get() + ySize + uvSize, buffer->DataV(), uvSize); m_nVideoW = width; m_nVideoH = height; // 刷新界面,触发paintGL接口 Q_EMIT PlayOneFrame();}当我们调用 PlayOneFrame 时,就可以通过 QOpenGL 来进行渲染 I420 YUV 数据了。6. 如何管理多个窗口的创建和销毁的可以通过管理 PeerConnection 那样管理 VideoRendererWidget ,还是定义一个 map std::map> video_renderer_widgets_;根据对方的 peerid 来进行缓存窗口,当需要添加窗口时: void MainWindow::addVideoRendererWidgetToMainWindow(std::string id, std::unique_ptr renderer){ const int itemsPerRow = 3; std::unique_ptr videoRenderer; if(renderer == nullptr) videoRenderer = std::make_unique(); else { videoRenderer = std::move(renderer); } videoRenderer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 计算新的位置 int count = ui->gridLayout->count(); int row = count / itemsPerRow; int column = count % itemsPerRow; // 添加到 gridLayout 中 ui->gridLayout->addWidget(videoRenderer.get(),row,column); //videoRenderer->setFixedSize(1280/3,720/3); // 将 widget 添加到 map 中 video_renderer_widgets_.insert({id, std::move(videoRenderer)});}当需要删除窗口时: void MainWindow::removeVideoRendererWidgetFromMainWindow(std::string id) { // 从 layout 中移除 widget,并删除它 // 查找 widget auto it = video_renderer_widgets_.find(id); if (it != video_renderer_widgets_.end()) { // 从 layout 中移除 widget ui->gridLayout->removeWidget(it->second.get()); // 从 map 中移除并删除 widget video_renderer_widgets_.erase(it); } }总结我们通过简短的描述和一些基础的 api 来介绍了如何通过 QT webrtc 来构建一个多人的音视频通话的项目。由于本人对 QT 不是太熟悉,所以 UI 上还有少许 Bug 。但不影响核心 API 调用。到此,通过本篇文章和之前的几篇文章我们已经实现了 Web 、Android 、Windows 之前的互通,后续会继续介绍 WebRTC 源码分析和实战项目的开发。原文 WebRTC 实战: QT for Windows 多人音视频通话天地劫幽城再临归真4-5攻略:第四章归真4-5八回合图文通关教学[多图],天地劫幽城再临归真4-5怎么样八回合内通
2024-04-19