《webrtc入门系列(一)easy_webrtc_server 入门环境搭建》
《webrtc入门系列(二)easy_webrtc_server 入门example测试》
《webrtc入门系列(三)云服务器coturn环境搭建》
《webrtc入门系列(四) zlmediakit webrtc sdp交互详细解读》
rtp_src_example
这个样例,非常的典型,对我们做设备端的同学来说,就需要一样这样的例子,模拟IPC发送视频到web播放。
环境
demo运行系统:Ubuntu20.04
播放环境:win10,chrom浏览器、edge浏览器
系统这里特别说下,我用Ubuntu18.04测试失败,打开文件错误,应该是Ubuntu18.04apt默认安装的ffmpeg的版本编译选项的问题。
解决办法,要么升级系统,要么自己源码安装ffmpeg。
测试过程
- 拷贝测试文件到build目录
rtp_src_example.cc里面是读了一个test.flv文件发送,作者工程里又只有一个test.h264,看代码是用ffmpeg接口打开的文件,所以格式没关系,ffmpge能自己解析,我们只要输入一个视频文件即可。拷贝test.h264到build目录,改名test.flv或者代码打开test.h264即可
cd easy_webrtc_server/build;cp ../test.h264 test.flv
2. 启动测试程序
记得sudo启动
./rtp_src_example 192.168.1.16 8000
将IP替换成你运行demo系统的IP
3. 打开网页播放视频
将easy_web_rtc目录下面index.html文件直接拖到浏览器打开,IP改成Ubuntu的ip地址,点call即可以看到视频。
chrom浏览器和Edge浏览器测试OK。
Firefox浏览器不允许跨源请求。
至此,就可以完整的抓包和分析代码学习了。
源码简读
muduo不支持udp,本项目基于muduo的Channel类简单封装一个udp通信的类;
基于muduo_http建立一个http信令服务器,交换webrtc所需要的sdp信息。
网页上打开一个http连接,服务器建立一个WebRtcTransport,传输层是一个UdpSocket。
WebRtcTransport生成sdp信息,通过http协议传到前端。
sdp信息包括媒体信息如编码格式、ssrc等,stun协议需要的ice-ufrag、ice-pwd、candidate,dtls需要的fingerprint。
前端通过candidate获取ip地址和端口号,通过udp协议连接到服务器的。
服务器收到udp报文,先后通过类UdpSocket接收报文;StunPacket和IceServer解析stun协议,此处的Stun协议解析,只要收到stun request,验证账户密码成功,就认为连接成功。
stun协议交互成功后,通过DtlsTransport进行dtls握手;交换密钥后就可以初始化SrtpChannel。此处没有通过签名验证客户端的证书,所以省略了前端返回sdp的步骤。
读取h264码流文件,通过ffmpeg生成rtp流,通过SrtpChannel加密,通过UdpSocket发送,前端就可以看到视频。
//起一个线程读取发送文件,WriteRtpCallback函数回调将数据通过webrtc的端口发送
std::thread flv_2_rtp_thread(
[&webrtc_session_factory]() { H2642Rtp("./test.h264", &webrtc_session_factory); });
//udp通道
UdpServer rtc_server(&loop, muduo::net::InetAddress("0.0.0.0", port), "rtc_server", 2);
//http信令服务器,交换sdp
HttpServer http_server(&loop, muduo::net::InetAddress("0.0.0.0", 8000), "http_server",
TcpServer::kReusePort);
//信令响应
rtc_server.SetPacketCallback([&webrtc_session_factory](UdpServer* server, const uint8_t* buf,
size_t len,
const muduo::net::InetAddress& peer_addr,
muduo::Timestamp timestamp) {
WebRTCSessionFactory::HandlePacket(&webrtc_session_factory, server, buf, len, peer_addr,
timestamp);
});
http_server.setHttpCallback(
[&loop, &webrtc_session_factory, port, ip](const HttpRequest& req, HttpResponse* resp) {
if (req.path() == "/webrtc") {
resp->setStatusCode(HttpResponse::k200Ok);
resp->setStatusMessage("OK");
resp->setContentType("text/plain");
//跨域
resp->addHeader("Access-Control-Allow-Origin", "*");
auto rtc_session = webrtc_session_factory.CreateWebRTCSession(ip, port);
//回复本地sdp
resp->setBody(rtc_session->webrtc_transport()->GetLocalSdp());
std::cout << rtc_session->webrtc_transport()->GetLocalSdp() << std::endl;
}
});
loop.runInLoop([&]() {
rtc_server.Start();
http_server.start();
});
loop.loop();