草庐IT

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

逝水流年丶轻染尘 2023-07-13 原文

文章目录

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端播放视频。

服务端流程图

  • 流程图如下
用户 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 = null
            var 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 = null
                player.destroy()
                delete player
                player = 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和监听,并创建JMuxer
            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');
                    });
                });
            }
# 停止播放,创建websocket和监听,并创建JMuxer
        function stop() {            
            if (websocket != null) {
                websocket.close()
                websocket = null
            }

            if (player != null) {
                var video = document.querySelector("#player");
                video.srcObject = null
                player.destroy()
                delete player
                player = null
            }
        }
  • web端界面展示
JMuxer

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

测试效果

下面开始展示测试效果

服务端环境

下图中,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. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  6. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  9. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  10. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

随机推荐