文章目录

  • websocket服务器
    • 前言
    • 本章节目标
    • 准备
    • 实现思路
      • 服务端流程图
    • 代码实现
      • 服务端
        • 服务端代码
      • web端
        • web端代码
          • JMuxer
    • 测试效果
      • 服务端环境
      • web端测试
    • 资源下载
    • 存在的问题

websocket服务器

前言

推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:

本章节目标

实现一个websocket 传输码流服务器
可以正常的传输h264裸流

准备

接着上一章,这里将在websocket服务器的基础上,实现传输h264裸流。这样,前端接收裸流,可以正常的进行播放。

实现思路

整体是采集到的数据经过编码,然后再通过websocket传输到前端。前端接收到数据后,就可以通过JMuxer库封装成fMP4喂给video标签,既可以实现web端播放视频。

服务端流程图

  • 流程图如下
#mermaid-svg-NIX3vJD1I3BVFucy {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NIX3vJD1I3BVFucy .error-icon{fill:#552222;}#mermaid-svg-NIX3vJD1I3BVFucy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NIX3vJD1I3BVFucy .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-NIX3vJD1I3BVFucy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NIX3vJD1I3BVFucy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NIX3vJD1I3BVFucy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NIX3vJD1I3BVFucy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NIX3vJD1I3BVFucy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NIX3vJD1I3BVFucy .marker.cross{stroke:#333333;}#mermaid-svg-NIX3vJD1I3BVFucy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NIX3vJD1I3BVFucy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NIX3vJD1I3BVFucy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NIX3vJD1I3BVFucy .actor-line{stroke:grey;}#mermaid-svg-NIX3vJD1I3BVFucy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NIX3vJD1I3BVFucy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NIX3vJD1I3BVFucy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NIX3vJD1I3BVFucy .sequenceNumber{fill:white;}#mermaid-svg-NIX3vJD1I3BVFucy #sequencenumber{fill:#333;}#mermaid-svg-NIX3vJD1I3BVFucy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NIX3vJD1I3BVFucy .messageText{fill:#333;stroke:#333;}#mermaid-svg-NIX3vJD1I3BVFucy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NIX3vJD1I3BVFucy .labelText,#mermaid-svg-NIX3vJD1I3BVFucy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NIX3vJD1I3BVFucy .loopText,#mermaid-svg-NIX3vJD1I3BVFucy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NIX3vJD1I3BVFucy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NIX3vJD1I3BVFucy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NIX3vJD1I3BVFucy .noteText,#mermaid-svg-NIX3vJD1I3BVFucy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NIX3vJD1I3BVFucy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NIX3vJD1I3BVFucy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NIX3vJD1I3BVFucy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NIX3vJD1I3BVFucy .actorPopupMenu{position:absolute;}#mermaid-svg-NIX3vJD1I3BVFucy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NIX3vJD1I3BVFucy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NIX3vJD1I3BVFucy .actor-man circle,#mermaid-svg-NIX3vJD1I3BVFucy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NIX3vJD1I3BVFucy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}用户websocket服务rtsp服务① 用户连接websocket服务② 开始拉取rtsp③ 开始读取h264文件并传输④ 响应的rtsp流⑤ 传输h264裸流⑥ 播放h264裸流用户websocket服务rtsp服务

代码实现

下面分别讲解服务端和客户端的问题

服务端

服务端主要有两个,一个rtsp服务,一个websocket服务。

服务端代码

下面来展示websocket服务器代码:
服务器关键代码:

  • Collection.h
#ifndef _COLLECTION_CFG_H_
#define _COLLECTION_CFG_H_#include <stdint.h>
#include <string>
#include "CollectionCfg.h"interface ICollection;
typedef std::shared_ptr<ICollection> spICollection;typedef std::function<void(uint8_t* data, int len)> NotifyVideo;interface COLLECTION_API ICollection
{virtual void start(const std::string& url) = 0;virtual void stop() = 0;
};interface COLLECTION_API CollectionFactory
{static spICollection createCollection(const NotifyVideo& video);
};#endif // _COLLECTION_CFG_H_
  • CollectionData.cpp
#include "CollectionData.h"CollectionData::CollectionData(const NotifyVideo& video): m_video(video), m_i32Len(1024000), m_bQuit(false), m_nVideoIndex(-1), m_iFmtCtx(nullptr)
{m_spBuffer.reset(new uint8_t[m_i32Len], std::default_delete<uint8_t[]>());av_register_all();avcodec_register_all();av_register_all();avformat_network_init();
}CollectionData::~CollectionData()
{release();
}void CollectionData::start(const std::string& url)
{int ret = 0;AVDictionary* opts = nullptr;av_dict_set(&opts, "buffer_size", "1024000", 0);av_dict_set(&opts, "max_delay", "50000", 0);av_dict_set(&opts, "stimeout", "20000000", 0);av_dict_set(&opts, "rtsp_transport", "tcp", 0);av_dict_set(&opts, "tune", "zerolatency", 0);av_dict_set(&opts, "preset", "superfast", 0);setQuit(false);if (url == ""){return;}do{if (ret = avformat_open_input(&m_iFmtCtx, url.c_str(), 0, &opts) < 0){fprintf(stderr, "Could not open input stream file '%s'", url.c_str());break;}ret = avformat_find_stream_info(m_iFmtCtx, nullptr);if (ret < 0){fprintf(stderr, "avformat_find_stream_info fail");break;}m_nVideoIndex = av_find_best_stream(m_iFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (m_nVideoIndex < 0){fprintf(stderr, "no video stream fail");break;}av_dump_format(m_iFmtCtx, 0, url.c_str(), 0);m_spThread.reset(new std::thread(std::bind(&CollectionData::doThread, this)));return;} while(0);release();
}void CollectionData::doThread()
{int ret = 0;AVPacket pkt;av_init_packet(&pkt);while (!m_bQuit){ret = av_read_frame(m_iFmtCtx, &pkt);if (pkt.stream_index != m_nVideoIndex){continue;}if (m_video){m_video(pkt.data, pkt.size);}av_packet_unref(&pkt);std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}void CollectionData::stop()
{setQuit(true);
}bool CollectionData::getQuit()
{return m_bQuit;
}void CollectionData::setQuit(bool bQuit)
{m_bQuit = bQuit;
}void CollectionData::thdReset()
{if (m_spThread){if (m_spThread->joinable()){m_spThread->join();}}m_spThread.reset();
}void CollectionData::release()
{setQuit(true);thdReset();if (m_iFmtCtx != nullptr){avformat_close_input(&m_iFmtCtx);}}

web端

web端代码

  • web端代码实现比较简单,只需要下面的几个:

创建video标签,用来显示视频。
创建websocket,用来收取h264流。
创建JMutex用来播放h264

  • index.html
<!DOCTYPE html>
<html><head><meta charset="utf-8"> 测试web播放</head><body><div><label>服务器  IP</label><input class="te" type="text" value = "127.0.0.1"  id="txtip"></div><div><label>服务器端口</label><input class="te" type="text" value = "8000"  id="txtport"></div><div><button id="play" type="button" onclick="play()">播放</button><button id="stop" type="button" onclick="stop()">停止</button></div><div><video id = "player" width="640" height="480" defaultPlaybackRate autoplay></video></div><script src="jmuxer.min.js"></script><script src="adapter-latest.js"></script><script type = "text/javascript">var player = nullvar websocket = null;function play() {if (player == null) {player = new JMuxer({node: 'player',mode: 'video',flushingTime: 200,fps: 30,debug: false})}if (websocket == null) {var ip = document.getElementById("txtip").value;var port = document.getElementById("txtport").value;var socketURL = "ws://" + ip + ":" + port;console.log("ws url: " + socketURL)websocket = new WebSocket(socketURL);websocket.binaryType = 'arraybuffer';websocket.addEventListener('message',function(event) {player.feed({video: new Uint8Array(event.data)})websocket.addEventListener('error', function(e) {console.log('Socket Error');});});}}function stop() {            if (websocket != null) {websocket.close()websocket = null}if (player != null) {var video = document.querySelector("#player");video.srcObject = nullplayer.destroy()delete playerplayer = null}}</script></body>
</html>
  • 代码讲解
# 分别是ip和端口输入框
<input class="te" type="text" value = "127.0.0.1"  id="txtip">
<input class="te" type="text" value = "8000"  id="txtport">
# 显示视频的标签页<video id = "player" width="640" height="480" defaultPlaybackRate autoplay></video>
# 开始播放,创建websocket和监听,并创建JMuxerfunction play() {if (player == null) {player = new JMuxer({node: 'player',mode: 'video',flushingTime: 200,fps: 30,debug: false})}if (websocket == null) {var ip = document.getElementById("txtip").value;var port = document.getElementById("txtport").value;var socketURL = "ws://" + ip + ":" + port;console.log("ws url: " + socketURL)websocket = new WebSocket(socketURL);websocket.binaryType = 'arraybuffer';websocket.addEventListener('message',function(event) {player.feed({video: new Uint8Array(event.data)})websocket.addEventListener('error', function(e) {console.log('Socket Error');});});}
# 停止播放,创建websocket和监听,并创建JMuxerfunction stop() {            if (websocket != null) {websocket.close()websocket = null}if (player != null) {var video = document.querySelector("#player");video.srcObject = nullplayer.destroy()delete playerplayer = null}}
  • web端界面展示
JMuxer

jmuxer可以播放h264裸流,同时可以播放音频,这个demo只是测试视频,不做音频处理

  • jmuxer 代码地址:
    https://github.com/samirkumardas/jmuxer.git

测试效果

下面开始展示测试效果

服务端环境

下图中,testRtspServer 是一个rtsp服务器。基于Qt和ffmpeg的抓屏rtsp服务(二)详细信息请查看这篇文章。WebSocketServed.exe 是拉取rtsp流,并利用ffmpeg获取h264裸流。通过websocket传输给前端。

web端测试

资源下载

代码下载

存在的问题

  • 客户端多次开关会导致服务器崩溃,需要后续解决
  • 该demo只支持h264播放,未加入音频,这个后续会加入
  • 该demo只处理h264播放,未加入h265播放。h265 1080p 25fps 流畅播放暂时不公开
  • 该程序只是demo,暂未处理其他影响稳定性问题

webrtc学习--websocket服务器(二) (web端播放h264)相关推荐

  1. netty 游戏服务器框图_基于Netty和WebSocket协议实现Web端自动打印订单服务方法与流程...

    本发明涉及电子商务技术领域,尤其涉及一种基于netty和websocket协议实现web端自动打印订单服务方法. 背景技术: 电子商务是以信息网络技术为手段,以商品交换为中心的商务活动:也可理解为在互 ...

  2. ssm项目实现上传视频,在web端播放视频

    实现的思路: 1,上传视频,将视频存放在服务器端,数据库中存放相对服务器的相对地址 2,网页端播放视频:使用ckplayer插件:www.ckplayer.com/ 参考:https://blog.c ...

  3. H265格式兼容各个浏览器web端播放方案

    可能有很多朋友会遇到H265格式的视频流无法播放,毕竟现在很多相机都支持h265了,确实有很多优点,但是它最大的问题就是很多浏览器无法播放,也有部分浏览器能够兼容h265,但是总不能让用户指定浏览器使 ...

  4. Servlet学习DAY_01:服务器概念/Web服务器的作用/ Servlet概念/ 如何关联和解除Tomcat/ 创建一个Web工程 /Servlet响应流程/ Get-Post /常见异常

    Servlet 什么是服务器 服务器就是一台高性能电脑 电脑上安装了提供服务的软件就称为 xxx服务器 举例: 邮件服务器: 就是在电脑上安装了提供邮件收发服务的软件 ftp服务器: 就是在电脑上安装 ...

  5. Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架

    一.基本介绍 WebSocket是HTML5的一种新通信协议,它实现了浏览器与服务器之间的双向通讯.而Socket.IO是一个完全由JavaScript实现.基于Node.js.支持WebSocket ...

  6. php在web端播放amr语音(如微信语音)

    在使用微信JSSDK的上传下载语音接口时,发现一个问题: 下载的语音在iPhone上不能播放,测试了之后原因竟然是: 微信接口返回的音频内容是amr格式的,但iPhone不支持播放此类型格式. 那么转 ...

  7. 腾讯实时音视频SDK[二]:web端实现

    文档地址 官方文档 安装引入 //安装 npm i trtc-js-sdk //要使用的页面进行引入 import TRTC from "trtc-js-sdk"; 效果图 电脑端 ...

  8. 常用的视频格式文件WEB端播放代码

    1.avi格式 代码片断如下: <object id="video" width="400" height="200" border= ...

  9. Web端播放 .amr音频文件,企业微信会话存档语音文件

    前言:amr格式是微信上的语音格式,比如企业微信会话存档语音文件保存时就会遇到,由于html标签都不支持amr格式的语音文件,因此采用如下开源项目: https://github.com/BenzLe ...

  10. Webassembly 学习3 -- 打造web端的aac 播放器

    1.引言 aac 是很常见的音频格式,压缩率比mp3 还高,H5 支持从audio 标签文件读取aac 文件并播放,但不支持从网络流中直接读取.这里借助webassembly 技术,将aac 转码成p ...

最新文章

  1. js立即执行函数: (function ( ){...})( ) 与 (function ( ){...}( ))
  2. 【Qt】DOM创建和操作XML文档
  3. 再见了kafka2.0时代,去掉了zk的kafka3.0才是时代新王!
  4. 导入依赖和加上注释后,lombok gettersetter识别不到
  5. linux文件IO——目录操作和文件属性
  6. mysql三表where查询_mysql三表查询sql语句
  7. 转载-C#委托之多播委托( 二)
  8. 进程与线程的区别?--多线程与线程池
  9. web安全这个行业的前景怎么样?
  10. Android学习小Demo(9)一个To Do List的实现
  11. vue-cli中理不清的assetsSubDirectory 和 assetsPublicPath
  12. 人月神话阅读笔记06
  13. 「洛谷5017」「NOIP2018」摆渡车【DP,经典好题】
  14. 聊聊spring security oauth2的password方式的认证
  15. 阿里云ACE认证学习知识点梳理
  16. gif录制软件 LICEcap
  17. 计算机考研数据库题库
  18. android 按钮边距,安卓button代码初始化默认内边距问题
  19. H5小游戏《看你有多色》扩展(辅助、眼力)
  20. 自学python需要安装什么-学习python要安装什么软件

热门文章

  1. Java中this的作用(简单说明)
  2. java中this和this()区别
  3. iterm2上传文件到linux,在iTerm2中使用Zmodem实现快速传输文件
  4. SecureCRT无法使用Zmodem上传下载文件
  5. 【计算机网络自顶向下方法】(哈工大)学习笔记
  6. RDPWrap远程桌面的一次脱坑
  7. Android 动画
  8. 【Axure报错】-Unable to connect to Axure Share. Please make sure you have an internet connection and try
  9. 制造业ERP系统具体操作流程是什么?
  10. 使用Mybatis拦截器实现数据分表