用QT中的webengine实现音视频传输(学习笔记)

前言

本文讲述的内容最后可以实现手机端在浏览器输入ip和端口号就可以直接拿到电脑端(Ubuntu端)的摄像头内容。本文以一个调试者的视角使用自签名证书,来完成音视频开发,中间很多内容会用各种方式去绕过证书的验证,这也是本文的精髓所在。

目录

前言

开发前的环境搭建

nginx服务器的搭建和部署

Ubuntu和qt的安装和部署

Ubuntu20.04的安装

qt5.12.4的下载

安装qt的特别注意:

模块的选择

在windows下实现视频传输(用于测试)

webrtc协议的简单介绍

信令(Signaling)

网络地址转换穿越(NAT Traversal)

API层

选择用浏览器内置webrtc的原因

webRTC的工作流程

信令交换

网络协商

媒体传输

开发总流程

​编辑

用js搭建websocket服务器(server.js)

安装node.js和websocket库

代码如下

实现pc.html

实现mobile.html

注意:pc.html和mobile.html中的ip地址要根据websocket的地址变化而改变

将pc.html,mobile.html部署到nginx上

效果演示

可能遇到的问题

在QT上用qt webengine模块实现电脑端功能

qwebengine模块的介绍

核心类QWebEngineView和QWebEnginePage

QWebEngineView

QWebEngineView简介

核心的api:

QWebEnginePage

QWebEnginePage简介

核心api

代码实现

效果演示

结语


正文开始之前先讲一下我的开发所用到的一些协议和工具,主要就是项目的方案。

1.webRTC协议,这个协议是实现音视频传输十分重要的协议,该协议采用的点对点协议,支持html5,所以绝大多数浏览器都内置了这个webRTC的库。但是确定就是建立点对点连接之前需要用其他的方式来交换信令,这里用的就是websocket。

2.websocket协议,该协议采用只能传输低延迟和二进制数据,而且是服务器和客户端的连接方式,所以可以很简单的就建立连接,并完成信令转发。

3.nginx服务器:后续代码需要用到HTML5,和JavaScript的语言,所以需要通过浏览器输入ip和端口来拿到服务器端的html文件。

4.https协议:由于要进行视频传输,并且要调用电脑端的摄像头这种敏感设备,所以http往往是不被浏览器支持的,但是作为开发调试不可能去申请ssl证书,所以本文以一个调试者的视角使用自签名证书,来完成音视频开发,中间很多内容会用各种方式去绕过证书的验证,这也是本文的精髓所在

开发前的环境搭建

nginx服务器的搭建和部署

Nginx介绍:Nginx是一个高性能的Web服务器、反向代理服务器、负载均衡器和HTTP缓存。可以处理静态内容(如HTML、CSS、图片、视频等)并将其响应给客户端。将客户端的请求转发给后端服务器,并将响应返回给客户端。

如果不会搭建的可以参考一下我的另一篇文章https://blog.youkuaiyun.com/2403_87069802/article/details/146415603?spm=1001.2014.3001.5502


Ubuntu和qt的安装和部署

版本选择:Ubuntu 20.04,QT5.12.4

先说明一下:小编之前用的是Ubuntu16.04和QT5.5,但是后面开发会遇到各种问题,比如qwebengine很多接口是在qt5.7引入的,后续也有介绍,所以小编也升级了qt和Ubuntu的版本中间也踩了很多的坑,所以这里也把我的安装流程附上,希望能让大家少踩点坑。

Ubuntu20.04的安装

参考这篇文章吧,个人觉得写的非常好。

https://blog.youkuaiyun.com/qq_42417071/article/details/136327674?fromshare=blogdetail&sharetype=blogdetail&sharerId=136327674&sharerefer=PC&sharesource=2403_87069802&sharefrom=from_link


qt5.12.4的下载

进入qt官网下载https://download.qt.io/official_releases/你需要的版本。

注意千万不要下载online-installer的版本,因为这个是qt官网给的一个下载工具,它运行之后会直接从官网上拉去资源包,但是qt官网在国外,如果Ubuntu网络配置没有搞好翻墙的话会一直显示下载失败,直接下载opensource版,这个可以直接在本地解压部署。

小编就下了两个版本,用online版搞了好几天没搞定,最后道心破碎了。

进入Ubuntu上运行./qt-opensource-linux-x64-5.12.4.run

可以看到如上的画面,根据指引完成登录,下载即可,这里不难就不多说了。

安装qt的特别注意:

要把qt安装到home目录下,不要安装到root目录下,因为后面需要用qt的webengine调用摄像头,而webengine采用的chromium的内核,其中的sandbox(沙盒检查)无法通过root权限调用,即root权限过高了,浏览器内核会认为有危险,所以后面都是以普通用户的身份来打开qt并运行qt,如果你安装到root目录下的话,普通用户就无法使用qt了,这也是小编踩过的坑,因为这个我后面发现不行的时候又重装了一遍qt。

模块的选择

安装最后一步会让你选择需要的模块,小编这里建议全选,不然后面也无法确定是否会用上其他模块,其实qt默认安装是没有webengine模块的,但是我现在开发突然要用到了,要加装的话会很麻烦,所以如果不是内存实在遭不住的话我建议全装了。


在windows下实现视频传输(用于测试)

因为在Ubuntu下用的webengine也就是浏览器的内核,而浏览器支持的是html5,css,javascript.故调用摄像头的核心接口是JavaScript的

navigator.mediaDevices.getUserMedia

所以在用Ubuntu拿到摄像头之前,可以先用windows下的浏览器先做测试,如果windows都无法成功调用摄像头,Ubuntu的qt上有一大堆的要处理的肯定更不行了。

webrtc协议的简单介绍


WebRTC协议涉及多个关键组件,它们共同协作,确保实时通信的顺利进行。

信令(Signaling)

作用:信令用于在WebRTC客户端之间协调、建立通信,包括会话控制(发起和结束)、网络数据(IP和端口)和媒体数据(编解码器、带宽和媒体类型等SDP信息)等元数据的交换。

网络地址转换穿越(NAT Traversal)

必要性:由于大多数用户位于路由器或防火墙后方,使用私网IP地址,为了实现P2P通信,需要穿透NAT和防火墙。

关键协议:

STUN(Session Traversal Utilities for NAT):轻量级服务器,用于帮助客户端了解自己在公网侧的IP地址和端口。对于“锥形NAT”等非对称NAT场景通常能成功。

TURN(Traversal Using Relays around NAT):中继服务器,当P2P连接无法直接建立时,提供数据转发服务,确保通信的可靠性。TURN会增加带宽成本和网络延迟,但可保证几乎所有复杂网络环境下的连接成功率。

ICE(Interactive Connectivity Establishment)框架:用于完成两客户端媒体协商后的网络连接建立。ICE收集所有可能的候选者(Candidate),包括本地IP地址、通过STUN或TURN服务器获得的公网IP地址和中继路径,交换候选者信息后,通过连通性检查确定最佳的媒体路径。

API层

WebRTC API:目前仅有JavaScript版本,提供了一套简单的接口,允许开发者在Web应用中直接调用浏览器提供的实时通信功能。

关键接口:

RTCPeerConnection:用于建立、维护和管理P2P连接,处理网络连接、音视频编解码、带宽管理等任务。

MediaStream:表示一个媒体数据流,包含音频轨道(AudioTrack)和视频轨道(VideoTrack),开发者可以将其添加到RTCPeerConnection中,通过网络发送到另一个WebRTC客户端。

getUserMedia:用于获取用户的音频和视频输入设备(如麦克风和摄像头)的权限,返回一个包含音视频流的对象。


选择用浏览器内置webrtc的原因

到这里很多人就要问了,我要怎么下载并调用webrtc的库呢,其实浏览器内置了webrtc的库,所以不需要自己去下载和配置,直接通过js调用其api接口即可了。当然后续在Ubuntu上开发的时候可以直接下载webrtc的源码库并交叉编译,但是光是下载webrtc的源码就很复杂了,webrtc的官网下载链接小编怀疑已经损坏了,小编试着下载了一个星期都没下成功,更不用说后面要交叉编译什么的 了,最后选择用浏览器的内核间接的调用webrtc即可了,这样省时省力。


webRTC的工作流程

信令交换

双方通过信令服务器交换会话描述(SDP)信息。SDP以键值对的形式描述媒体会话,包括媒体类型、编解码器、分辨率、带宽等。

一端生成Offer(SDP),描述自身希望发送或接收的媒体类型、可用的编码参数等,通过信令服务器发送给另一端。

另一端收到Offer后,生成Answer(SDP),确认可接受的媒体类型与参数,并返回给发起方。

网络协商

双方通过ICE框架收集候选地址,包括主机候选(设备自身IP/端口)、反射候选(通过STUN服务器获取的公网映射地址)、中继候选(通过TURN服务器获取的中继地址)。

双方交换候选地址信息,ICE进行连通性测试,尝试通过不同的候选地址建立连接。

一旦某组候选地址测试成功,即可作为连接的最终通信路径。ICE后续还可动态监测网络状况并进行切换。

媒体传输

使用RTP/SRTP协议传输音视频数据,确保数据的实时性和安全性。

使用RTCP协议监控传输质量,根据网络状况调整传输策略。

通过数据通道传输任意类型的数据,实现低延迟的点对点交互。


开发总流程

  1. 用websocket搭建信令服务器
  2. 用js和html完成pc.html调用电脑摄像头
  3. 用js完成mobile.html在手机端接收视频流
  4. 将pc.html和mobile.html放在nginx服务器上进行代理,用户可以直接通过手机/电脑浏览器访问到pc.html和mobile.html

用js搭建websocket服务器(server.js)

安装node.js和websocket库

node.js即运行JavaScript的工具,可以让你随时随地运行js程序

npm是Node.js的包管理器,它允许你从npm注册表安装、发布和管理Node.js包,websocket就需要npm来管理

websocket是一个外部的库。

sudo apt install nodejs
sudo apt install npm
npm install ws
代码如下
const WebSocket = require('/usr/local/nodejs/node_modules/ws');
const fs = require('fs');
const https = require('https');

// 加载 SSL 证书和私钥
const server = https.createServer({
    cert: fs.readFileSync('/usr/local/nginx/ssl/nginx-selfsigned.crt'),
    key: fs.readFileSync('/usr/local/nginx/ssl/nginx-selfsigned.key')
});

// 创建 WebSocket 服务器
const wss = new WebSocket.Server({ server });

//有用户连接时触发
wss.on('connection', (ws) => {
    console.log('A new client connected!');
//受到消息时触发
    ws.on('message', (message) => {
        console.log('Received:', message);

        // 假设 message 是 JSON 格式的字符串
        const data = JSON.parse(message);

        // 广播消息给所有客户端
        wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(JSON.stringify(data)); // 确保发送的是 JSON 格式的文本
            }
        });
    });

    ws.on('close', () => {
        console.log('A client disconnected.');
    });
});

// 启动 HTTPS 服务器
server.listen(8888, () => {
    console.log('WebSocket server is running on wss://localhost:8888');
});

代码很简单,不需要过多的介绍,要注意的就是加载外部模块时要注意路径,而且websocket在本地运行的话,ip就是本地的地址,即虚拟机的地址,用ifconfig可以查询

WebSocket:从指定的路径引入 ws 模块,这是一个实现 WebSocket 协议的库。

fs:引入 Node.js 的文件系统模块,用于读取文件。

https:引入 Node.js 的 HTTPS 模块,用于创建 HTTPS 服务器。

写完代码之后到对应的路径下运行node server.js即可,这样就表示websocket服务器正在工作中了


实现pc.html

核心接口就是getUserMedia,然后配合上信令服务器的一些接口。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC Camera (PC)</title>
    <style>
	#localVideo
	{
		background-color:#CF3;	
	}
	
	
	</style>
    
    
</head>
<body>
    <video id="localVideo" autoplay playsinline muted></video>

    <script>
	//console.log('aklsdhgklhfgkjhkdfgh');
        const localVideo = document.getElementById('localVideo');

        let localStream;
        let peerConnection;
        const ws = new WebSocket('wss://192.168.0.121:8888'); // 替换为信令服务器的 IP 和端口

        // 初始化 RTCPeerConnection
        function createPeerConnection() {
            peerConnection = new RTCPeerConnection({
                iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // 使用 Google 的公共 STUN 服务器
            });

            // 处理远程视频流
            peerConnection.ontrack = (event) => {
                console.log('Received track:', event.track);
                if (event.track.kind === 'video') {
                    const video = document.getElementById('remoteVideo');
                    video.srcObject = event.streams[0];
                    video.play(); // 确保视频播放
                }
            };

            // 处理 ICE Candidate
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    ws.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
                }
            };
        }

        // WebSocket 消息处理
        ws.onmessage = async (message) => {
            const data = JSON.parse(message.data);
            console.log('Received data:', data);

            if (data.type === 'answer') {
                // 收到 Answer,设置远程描述
                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
            } else if (data.type === 'candidate') {
                // 收到 ICE Candidate,添加到连接中
                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
            }
        };

        // 启动摄像头
        async function startCamera() {
            try {
				const constraints = {
					video: true,
					audio: false // 明确禁用音频
				};
                localStream = await navigator.mediaDevices.getUserMedia(constraints)
                localVideo.srcObject = localStream;
				console.log('successs open video');
                // 创建 RTCPeerConnection
                createPeerConnection();

                // 添加本地视频流到连接中
                localStream.getTracks().forEach(track => {
                    peerConnection.addTrack(track, localStream);
                });

                // 创建 Offer
                const offer = await peerConnection.createOffer();
                await peerConnection.setLocalDescription(offer);

                // 发送 Offer 到信令服务器
                ws.send(JSON.stringify({ type: 'offer', offer: offer }));
            } catch (error) {
                console.error('Error accessing camera:', error);
            }
        }

        // 当 WebSocket 连接成功后,自动启动摄像头
        ws.onopen = () => {
            startCamera();
        };
		
//C++ 调用showalert函数
			function showalert() 
			{
				alert("asdfg")
			}
			//C++ 调用getJsData函数
function getJsData() 
{
	return "C++ Call JS demo"
}	

		
    </script>
</body>
</html>

实现mobile.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC Camera (Mobile)</title>
</head>
<body>
    <video id="remoteVideo" autoplay playsinline></video>

    <script>
        const remoteVideo = document.getElementById('remoteVideo');

        let peerConnection;
        const ws = new WebSocket('wss://192.168.0.121:8888'); // 替换为信令服务器的 IP 和端口

        // 初始化 RTCPeerConnection
        function createPeerConnection() {
            peerConnection = new RTCPeerConnection({
                iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // 使用 Google 的公共 STUN 服务器
            });

            // 处理远程视频流
            peerConnection.ontrack = (event) => {
                console.log('Received track:', event.track);
                if (event.track.kind === 'video') {
                    remoteVideo.srcObject = event.streams[0];
                    remoteVideo.play(); // 确保视频播放
                }
            };

            // 处理 ICE Candidate
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    ws.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
                }
            };
        }

        // WebSocket 消息处理
        ws.onmessage = async (message) => {
            const data = JSON.parse(message.data);
            console.log('Received data:', data);

            if (data.type === 'offer') {
                // 创建 RTCPeerConnection
                createPeerConnection();//这样才能保证在初始化RTCPeerConnection时就设置ontrack

                // 设置远程描述
                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));

                // 创建 Answer
                const answer = await peerConnection.createAnswer();
                await peerConnection.setLocalDescription(answer);

                // 发送 Answer 到信令服务器
                ws.send(JSON.stringify({ type: 'answer', answer: answer }));
            } else if (data.type === 'candidate') {
                // 收到 ICE Candidate,添加到连接中
                if (peerConnection) {
                    await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
                }
            }
        };
    </script>
</body>
</html>

注意:pc.html和mobile.html中的ip地址要根据websocket的地址变化而改变


将pc.html,mobile.html部署到nginx上

修改nginx.conf,如下图所示,ip为localhost,端口为9300采用的https服务,pc和mobile共用一个端口,输入时只需要:https://192.168.0.111:9300/pc这样即可


效果演示

电脑端:由于是自签名证书,浏览器访问时可能会弹出警告,不过忽略就好了。


手机端:由于只是测试,手机端的视频大小有点溢出,不过后续处理一下就好了,这个就先不管了。

信令服务器端

可以看到有新用户连接和数据的转发。

可能遇到的问题

这个是浏览器的一个保护机制,即用户没有和浏览器交互(play()),只需要点击页面,刷新即可。


至此,最核心的测试基本上就完成了。

在QT上用qt webengine模块实现电脑端功能

qwebengine模块的介绍

最好的学习方式一定是官网,我只摘出该功能需要用到的类和接口,但是要系统学习了解还是要慢慢品官网。

qwebengine指引官网链接:https://doc.qt.io/qt-5/qtwebengine-index.html

进入官网之后找到Qt WebEngine Features,点击之后可以看到目录,然后可以找到我们需要的webRTC索引。小编用了翻译工具,qt的官网是全英文的,所以我截图出来的可能存在翻译错误,这个不用太过于在意。


这里可以找到webRTC的官方指引(如下图),经过一系列的翻找阅读,发现最重要的就QWebEngineViewQWebEnginePage这两个类

核心类QWebEngineView和QWebEnginePage

官方指引:https://doc.qt.io/qt-5/qwebengineview.html

QWebEngineView
QWebEngineView简介

定位:作为视图层组件,直接用于在界面上显示网页内容。

继承自 QWidget,可像普通控件一样嵌入到 Qt 界面布局中。

提供完整的浏览器视图功能,包括导航按钮、地址栏(需自行实现)等。

支持加载本地 HTML、远程 URL 或直接渲染字符串内容。

核心的api:
  1. void QWebEngineView::setPage(QWebEnginePage *page)这个接口可以将界面设置成我想要的Page

  2. void QWebEngineView::setHtml(const QString &html, const QUrl &baseUrl = QUrl())这个接口可以直接加载网页,后续也是用这个直接调用我的pc.html

  3. QWebEngineSettings *QWebEngineView::settings() const;这个接口可以修改设置,类似浏览器的设置功能,后面就是用这个修改设置,使其支持JavaScript和webrtc广域网连接。几乎全部设置都可以通过这个枚举类型来改变,下图只截取了一部分。

QWebEnginePage
QWebEnginePage简介

QWebEnginePage 定位:作为控制层组件,管理网页的加载、渲染和后台逻辑。 功能: 处理网络请求、资源加载、JavaScript 执行和页面生命周期。 支持自定义请求头、Cookie、代理设置和证书验证。 提供与页面 JavaScript 交互的接口(如执行脚本、接收回调)。 控制页面渲染设置(如缩放比例、字体、用户代理字符串)。

核心api

这个信号就是当你要调用摄像头时会触发的一个信号,即向你申请权限,利用这个信号绑定槽函数,就可以申请到摄像头和麦克风的权限。如下图所示,可以看到有几个关联的类,这几个类其实都需要熟悉,他们互相调用,QWebEnginePage::Feature,setFeaturePermission()


void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature, QWebEnginePage::PermissionPolicy policy)

这个就是设置权限的函数

这个枚举类型就是权限的确定了。


bool QWebEnginePage::certificateError(const QWebEngineCertificateError &certificateError)

这个虚函数就是验证ssl证书的函数,可以看到return true就是忽略错误,但是默认是return false。所以,要忽略证书错误最重要的就是重写这个函数

代码实现

实现流程:和上面在windows的测试一样,唯一不一样的就是pc.html放在qt上用seturl来跑了,页面显示从浏览器页面变成了webengineview了

在.pro中

QT       += core gui webenginewidgets quick webengine

然后重写QWebenginePage。

文件名为:debugwebengine.cpp

#include "debugwebengine.h"

debugwebengine::debugwebengine(QObject* parent): QWebEnginePage(parent)
{
    //qDebug() << "debugwebengine initialized.";
     connect(this, &QWebEnginePage::featurePermissionRequested, this, &debugwebengine::onFeaturePermissionRequested);

}
//重写这个函数以保证它跳过ssl证书验证
bool debugwebengine::certificateError(const QWebEngineCertificateError &certificateError)
{
    return true;
}
void debugwebengine::onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature) {
    //qDebug() << "Granted permission for feature:" ;
        // 检查请求的功能权限
        if (feature == QWebEnginePage::MediaAudioCapture || feature == QWebEnginePage::MediaVideoCapture) {
            // 批准摄像头和麦克风的访问权限
            setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser);
            qDebug() << "Granted permission for feature:" << feature << "from origin:" << securityOrigin.toString();
        } else {
            setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser);//所有权限都给
            qDebug() << "Denied permission for feature:" << feature << "from origin:" << securityOrigin.toString();
        }
    }

void debugwebengine::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) {
    // 将 JavaScript 的 console 输出转发到 Qt 的日志系统
    QString logMessage = QString("JS Console: %1 (Line: %2, Source: %3)").arg(message).arg(lineNumber).arg(sourceID);
    switch (level) {
        case InfoMessageLevel:
            qInfo() << logMessage;
            break;
        case WarningMessageLevel:
            qWarning() << logMessage;
            break;
        case ErrorMessageLevel:
            qCritical() << logMessage;
            break;
        default:
            qDebug() << logMessage;
            break;
    }
}

我加入了一些代码可以让qt和js交互,当然这里是简单交互,要让qt调用js函数或者js调用qt需要额外做很多处理,这里就不赘述了。

debugengine.h

#ifndef DEBUGWEBENGINE_H
#define DEBUGWEBENGINE_H

#include <QWebEnginePage>

class debugwebengine:public QWebEnginePage{
    Q_OBJECT

public:
    explicit debugwebengine(QObject* parent = nullptr);
    ~debugwebengine()override{}
protected:
    virtual bool certificateError(const QWebEngineCertificateError &certificateError)override;
 private slots:
    void onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature);
    void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID);

};

#endif // DEBUGWEBENGINE_H

main.cpp

#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QUrl>
#include <QDebug>
#include"debugwebengine.h"
#include <cstdlib> // 用于 setenv

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    setenv("QTWEBENGINE_DISABLE_SANDBOX", "1", 1);
    //qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--enable-logging --v=1");
    // 创建Web视图(局部变量,无需全局变量)
    QWebEngineView view;

    // 基础配置
    view.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
   // view.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);//这个是屏幕捕获(录屏),不是打开摄像头权限
   view.settings()->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, false);//修改了WebRTC的设置,使其不仅限于使用公共网络接口,从而可能允许在局域网内的设备之间建立更直接的WebRTC连接。
   debugwebengine* page = new debugwebengine(&view);

   view.setPage(page);
    view.resize(1024, 768);

    // 加载本地HTML文件(更安全的方式)
   //const QString htmlPath = "/mnt/hgfs/aic_jeff/final_project/2.画面获取显示/pc.html";
   // view.setUrl(QUrl::fromLocalFile(htmlPath));  // 自动处理文件路径格式
    const QString htmlUrl = "https://192.168.0.111:9300/pc";
     view.setUrl(QUrl(htmlUrl));
    view.show();
    return app.exec();
}



唯一需要讲解的就是

setenv("QTWEBENGINE_DISABLE_SANDBOX", "1", 1);//关闭沙盒模式,关闭沙盒模式可以减少很多bug,因为这个安全检测其实很烦。


效果演示

首先要确保你的摄像头连接到了Ubuntu上,默认虚拟机是不共享摄像头的,小编这里直接外接了一个摄像头,如果要共享主机摄像头的话,建议各位自己去改一下配置什么的,小编没试过。因为这个摄像头踩了无数的坑,之前一直显示打不开摄像头,找了好久发现是虚拟机没有摄像头。

qt运行画面如下

手机端运行画面如下


结语

至此,整个项目就完成了,这个项目结合了js,qt,web服务器,webrtc等模块实现了音视频的开发,整体难度还是不小的,当然这个功能还有很多有问题的地方,小编也还在学习阶段,欢迎大家留言讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值