gstreamer 概述以及TX1 硬解码多路RTSP流

2023-05-16

以NVIDIA TX1为例硬解码就是利用硬件芯片来解码的,TX1有单独的解码模块,NVDEC.
软解码是用软件程序来解码,比较占用CPU资源
查看cpu gpu 以及编解码模块的使用:
sudo ./tegrastats

1.gstreamer概述

Gstreamer是一个libraries和plugins的集合,用于帮助实现各种类型的多媒体应用程序,比如播放器,转码工具,多媒体服务器等。
利用Gstreamer编写多媒体应用程序,就是利用elements构建一个pipeline。element是一个对多媒体流进行处理的object,比如如下的处理:
*读取文件。
*不同格式的编解码。
*从硬件采集设备上采集数据。
*在硬件设备上播放多媒体。
*多个流的复用。
elements的输入叫做sink pads,输出叫做source pads。应用程序通过pad把element连接起来构成pipeline,如下图所示,其中顺着流的方向为downstream,相反方向是upstream。
pipeline的输入是 src, 输出是sink

应用程序会收到来自pipeline的消息和通知,比如EOS等。
总体设计

Gstreamer的设计目标如下:
快速处理大规模数据。
对多线程处理的完全支持。
能处理各种格式的流媒体。
不同数据流的同步。
处理多种设备的能力。

基于Gstreamer的应用程序能够具备的处理能力依赖于系统中安装的不同种类功能的elements的数量。

Gstreamer核心不具备处理具体的media的功能,但是element处理media时需要具备的特性很多是由Gstreamer的核心提供的。
elements

element是pipeline的最小组成部分。element提供了多个pads,或者为sink,或者为source。一个element有四种可能的状态,分别是NULL,READY,PAUSED,PLAYING。NULL和READY状态下,element不对数据做任何处理,PLAYING状态对数据进行处理,PAUSE状态介于两者之间,对数据进行preroll。应用程序通过函数调用控制pipeline在不同状态之间进行转换。
element的状态变换不能跳过中间状态,比如不能从READY状态直接变换到PLAYING状态,必须经过中间的PAUSE状态。

element的状态转换成PAUSE会激活element的pad。首先是source pad被激活,然后是sink pad。pad被激活后会调用activate函数,有一些pad会启动一个Task。

PAUSE状态下,pipeline会进行数据的preroll,目的是为后续的PLAYING状态准备好数据,使得PLAYING启动的速度更快。一些element需接收到足够的数据才能完成向PAUSE状态的转变,sink pad只有在接收到第一个数据才能实现向PAUSE的状态转变。

通常情况下,element的状态转变需要协调一致。

可对element进行如下分类:
source,只提供数据源。
sink,比如播放设备。
transform
demuxer
muxer

Bin

bin是由多个element构成的特殊的element,用图来说明:

Pipeline
pipeline是具备如下特性的特殊的bin:
选择并管理一个全局的时钟。
基于选定的时钟管理running_time。running_time用于同步,指的是pipeline在 PLAYING状态下花费的时间。
管理pipeline的延迟。
通过GstBus提供element与应用程序间的通讯方式。
管理elements的全局状态,比如EOS,Error等。

Dataflow and buffers

Gstreamer支持两种类型的数据流,分别是push模式和pull模式。在push模式下,upstream的element通过调用downstream的sink pads的函数实现数据的传送。在pull模式下,downstream的element通过调用upstream的source pads的函数实现对数据的请求。

push模式是常用的模式,pull模式一般用于demuxer或者低延迟的音频应用等。

在pads之间传送的数据封装在Buffer里,Buffer中有一个指向实际数据的指针以及一些metadata。metadata的内容包括:
Timestamp
Offset
Duration
media type
其它

在push模式下,element通过调用gst_pad_push()函数把buffer传送给对应的pad。在pull模式下,element通过调用gst_pad_pull_range()函数把pull过来。

element在push buffer之前需要确认对应的element具备处理buffer中的数据类型的能力。在传说红之前首先查询对应的element能够处理的格式的种类,并从中选择合适的格式,通过gst_buffer_set_caps()函数对buffer进行设置,然后才传送数据。

收到一个buffer后,element要首先对buffer进行检查以确认是否能够处理。

可以调用gst_buffer_new()函数创建一个新的buffer,也可以调用gst_pad_alloc_buffer()函数申请一个可用的buffer。采用第二种方法接收数据的buffer可以设定接收其它类型的数据,这是通过对buffer的caps进行设定来实现的。

选择媒体类型并对buffer进行设定的处理过程叫做caps negotianation。

Caps

Caps,也就是媒体类型,采用key/value对的列表来描述。key是一个字符串类型,value的类型可能是int/float/string类型的single/list/range。

Data flow and events

除了数据流,还有events流。与数据流不同,events的传送方向既有downstream的,也有upstream的。

events用于传递EOS,flushing,seeking等消息。

有的events必须和data flow一起进行serialized。serialized的events比如TAG,非serialized的events比如FLUSH。

Pipeline construction

gst_pipeline_create()函数用于创建一个pipeline,gst_bin_add()函数用于向pipeline中添加element,gst_bin_remove()函数用于从pipeline中移除element。gst_element_get_pad()函数用于检索pipeline中的element。gst_pad_link()函数用于把pads连接在一起。

有的element会在数据流开始传送的时候创建新的pads,通过调用函数g_signal_connect()函数,能在新的pads被创建的时候接收到消息。

由于处理的数据互相不兼容,有的elements是不能被连接到一起的。gst_pad_get_caps()函数查询element能够处理的数据类型。

Pipeline clock

Pipeline的一个重要功能是为pipeline中的所有elements选择一个全局时钟。

时钟的作用是提供一个每秒为GST_SECOND的单调递增的时钟,单位是纳秒。element利用这个时钟时间来播放数据。

在pipeline被设为PLAYING之前,pipeline查询每一个element是否能提供clock,并按照如下次序来选择clock:
应用程序选择了一个clock。
如果source element提供了clock。
其它任何提供了clock的element。
选择一个默认的系统clock。

也有特殊的情况,比如存在音频sink提供了clock,那么就选择其提供的clock。


Pipeline states

完成了pads的链接和signals的链接,就可以设定pipeline为PAUSED状态启动数据流的处理。当bin(这里指的是pipeline)进行状态转换的时候要转换所有的children的状态,转换的次序是从sink element开始到source element结束,这样做的目的是为了确保upstream element提供数据的时候,downstream element已经准备好。

Pipeline status

Pipeline会通过bus向应用程序通报发生的events。bus是由pipeline提供的一个object,可以通过gst_pipeline_get_bus()函数取得。

bus分布到加入pipeline的每一个element。element利用bus来发布messages。有各种不同类型的messages,比如ERRORS,WARNINGS,EOS,STATE_CHANGED等。

pipeline以特殊的方式处理接收到的EOS message,只有当所有的sink element发送了EOS message的时候,pipeline才会把EOS发送给应用程序。

也可以通过gst_element_query()函数获取pipeline status,比如获取当前的位置或者播放的时间。

Pipeline EOS

当source filter遇上了流结束,会沿着downstream的方向向下一个element发送一个EOS的event,这个event依次传送给每一个element,接收到EOS event的element不再接收数据。

启动了线程的element发送了EOS event后就不再发送数据。

EOS event最终会到达sink element。sink element会发送一个EOS消息,通告流结束。pipeline在接收到EOS消息以后,把消息发送给应用程序。只有在PLAYING状态下会把EOS的消息传送给应用程序。

发送了EOS以后,pipeline保持PLAYING状态,等待应用程序把pipeline的状态置为PAUSE或者READY。应用程序也可以进行seek操作。

2, Gstreamer解码

1>调用Gstreamer解码多路rtsp(部分代码)

/*
*Author:mxj
*/

#ifndef __GSTREAMER_CAMERA_H__
#define __GSTREAMER_CAMERA_H__

#include <gst/gst.h>
#include <string>

struct _GstAppSink;//声明结构体和类
class QWaitCondition;
class QMutex;
/*** gstreamer CSI camera using nvcamerasrc (or optionally v4l2src)
* @ingroup util
*/
class gstCamera
{
public:
    // 创建camera类
    static gstCamera* Create( int v4l2_device=-1 ); // use onboard camera by default (>=0 for V4L2)
    static gstCamera* Create( uint32_t width, uint32_t height, int v4l2_device=-1 );

    // 析构函数
    ~gstCamera();

    // 开始和停止流
    bool Open();
    void Close();

    // 采集YUV(NV12格式)
    bool Capture( void** cpu, void** cuda, unsigned long timeout=ULONG_MAX );

    // 抓取YUV-NV12 CUDA image, 转换成 float4 RGBA (像素范围在 0-255)
    // 转换如果在CPU上进行,设置zeroCopy=true,默认只在CUDA上.
    bool ConvertRGBA( void* input, void** output, bool zeroCopy=false );

    // 图像大小信息 inline(内联函数,适合简单的函数)
    inline uint32_t GetWidth() const     { return mWidth; }
    inline uint32_t GetHeight() const    { return mHeight; }
    inline uint32_t GetPixelDepth() const { return mDepth; }
    inline uint32_t GetSize() const      { return mSize; }

    // 默认图像大小,可以在create时改变
    static const uint32_t DefaultWidth = 1280;
    static const uint32_t DefaultHeight = 720;

private:
    static void onEOS(_GstAppSink* sink, void* user_data);
    static GstFlowReturn onPreroll(_GstAppSink* sink, void* user_data);//GstFlowReturn 传递流
    static GstFlowReturn onBuffer(_GstAppSink* sink, void* user_data);

    gstCamera();

    bool init();
    bool buildLaunchStr();
    void checkMsgBus();
    void checkBuffer();
    //GstBus
    _GstBus* mBus;//GstBus 异步同步消息
    _GstAppSink* mAppSink;
    _GstElement* mPipeline;

    std::string mLaunchStr="rtspsrc location=rtsp://admin:buaa123456@192.168.1.106:554/h264/ch1/main/av_stream latency=0 ! queue ! rtph264depay ! h264parse ! queue ! omxh264dec ! appsink name=mysink";
    uint32_t mWidth;
    uint32_t mHeight;
    uint32_t mDepth;
    uint32_t mSize;

    static const uint32_t NUM_RINGBUFFERS = 16;//环形队列来解决数据阻塞问题

    void* mRingbufferCPU[NUM_RINGBUFFERS];
    void* mRingbufferGPU[NUM_RINGBUFFERS];

    QWaitCondition* mWaitEvent;
    //mutex.lock() //锁住互斥量(mutex)。如果互斥量是解锁的,那么当前线程就立即占用并锁定它。否则,当前线程就会被阻塞,知道掌握这个互斥量的线程对它解锁为止。
    //mutex.unlock()//解锁
    //mutex.tryLock()//尝试解锁,如果该互斥量已经锁住,它就会立即返回
    QMutex* mWaitMutex;
    QMutex* mRingMutex;

    uint32_t mLatestRGBA;
    uint32_t mLatestRingbuffer;
    bool mLatestRetrieved;

    void* mRGBA[NUM_RINGBUFFERS];
    int mV4L2Device;  // -1 for onboard, >=0 for V4L2 device

    inline bool onboardCamera() const       { return (mV4L2Device < 0); }
};

#endif




bool gstCamera::Capture( void** cpu, void** cuda, unsigned long timeout )
{
    /*wait() 函数必须传入一个已上锁的 mutex 对象,在 wait() 执行过程中,
    mutex一直保持上锁状态,直到调用操作系统的wait_block 在阻塞的一瞬间把 mutex 解锁
    (严格说来应该是原子操作,即系统能保证在真正执行阻塞等待指令时才解锁)。
    另一线程唤醒后,wait() 函数将在第一时间重新给 mutex 上锁(这种操作也是原子的)
    ,直到显示调用 mutex.unlock() 解锁。*/
    mWaitMutex->lock();
const bool wait_result = mWaitEvent->wait(mWaitMutex, timeout);
mWaitMutex->unlock();

    if( !wait_result )
        return false;

    mRingMutex->lock();
    const uint32_t latest = mLatestRingbuffer;
    const bool retrieved = mLatestRetrieved;
    mLatestRetrieved = true;
    mRingMutex->unlock();

    // skip if it was already retrieved
    if( retrieved )
        return false;

    if( cpu != NULL )
        *cpu = mRingbufferCPU[latest];

    if( cuda != NULL )
        *cuda = mRingbufferGPU[latest];

    return true;
}

#define release_return { gst_sample_unref(gstSample); return; }

// checkBuffer
void gstCamera::checkBuffer()
{
    bool write_flags=true;//默认写数据
    if( !mAppSink )
        return;

    // block waiting for the buffer 函数被唤醒until A sample or EOS 可用 或者appsink 被设置成 ready/null state
    GstSample* gstSample = gst_app_sink_pull_sample(mAppSink);

    if( !gstSample )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_app_sink_pull_sample() returned NULL...\n");
        return;
    }
    //get buffer from gstSample
    GstBuffer* gstBuffer = gst_sample_get_buffer(gstSample);

    if( !gstBuffer )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_sample_get_buffer() returned NULL...\n");
        return;
    }

    // retrieve
    GstMapInfo map; 

    if( !gst_buffer_map(gstBuffer, &map, GST_MAP_READ) ) 
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer_map() failed...\n");
        return;
    }

    //gst_util_dump_mem(map.data, map.size); 

    void* gstData = map.data; //GST_BUFFER_DATA(gstBuffer);
    const uint32_t gstSize = map.size; //GST_BUFFER_SIZE(gstBuffer);

    if( !gstData )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL data pointer...\n");
        release_return;
    }

    // 取出caps
    GstCaps* gstCaps = gst_sample_get_caps(gstSample);

    if( !gstCaps )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL caps...\n");
        release_return;
    }

    GstStructure* gstCapsStruct = gst_caps_get_structure(gstCaps, 0);

    if( !gstCapsStruct )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_caps had NULL structure...\n");
        release_return;
    }

    // get width & height of the buffer
    int width = 0;
    int height = 0;

    if( !gst_structure_get_int(gstCapsStruct, "width", &width) ||
        !gst_structure_get_int(gstCapsStruct, "height", &height) )
    {
        printf(LOG_GSTREAMER "gstreamer camera -- gst_caps missing width/height...\n");
        release_return;
    }

    if( width < 1 || height < 1 )
        release_return;

    mWidth = width;
    mHeight = height;
    mDepth = (gstSize * 8) / (width * height);
    mSize = gstSize;

    //printf(LOG_GSTREAMER "gstreamer camera recieved %ix%i frame (%u bytes, %u bpp)\n", width, height, gstSize, mDepth);

    // make sure ringbuffer is allocated
    if( !mRingbufferCPU[0] )
    {
        for( uint32_t n=0; n < NUM_RINGBUFFERS; n++ )
        {
            if( !cudaAllocMapped(&mRingbufferCPU[n], &mRingbufferGPU[n], gstSize) )
                printf(LOG_CUDA "gstreamer camera -- failed to allocate ringbuffer %u (size=%u)\n", n, gstSize);
        }

        printf(LOG_CUDA "gstreamer camera -- allocated %u ringbuffers, %u bytes each\n", NUM_RINGBUFFERS, gstSize);
    }

    // copy to next ringbuffer
    const uint32_t nextRingbuffer = (mLatestRingbuffer + 1) % NUM_RINGBUFFERS;      

    //printf(LOG_GSTREAMER "gstreamer camera -- using ringbuffer #%u for next frame\n", nextRingbuffer);
    memcpy(mRingbufferCPU[nextRingbuffer], gstData, gstSize);
    // FILE *fp=fopen("out.yuv","w+");
    // fwrite(gstData,gstSize,1,fp);
    // fclose(fp);
    //test h264 write
    //void *writedata=map.data;
    // while(write_flags==true)
    // {
    //  FILE *fp=fopen("out.264","a+");
    //  fwrite(writedata,gstSize,1,fp);
    //  write_flags=false;
    //  fclose(fp);
    // }
    gst_buffer_unmap(gstBuffer, &map); 
    //gst_buffer_unref(gstBuffer);
    gst_sample_unref(gstSample);


    // update and signal sleeping threads
    mRingMutex->lock();
    mLatestRingbuffer = nextRingbuffer;
    mLatestRetrieved = false;
    mRingMutex->unlock();
    mWaitEvent->wakeAll();
}

可以在带显示的时候解码4路1080rtsp流.
不加显示可以做到6路1080p解码

2>opencv中使用Gstreamer解码海康rtsp摄像头

A.安装gstreamer依赖

B.重新编译安装opencv
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D CUDA_GENERATION=Kepler ..
C.确认opencv cmake后的提示中Gstreamer的五个选项都为on
D.编译opencv之后的使用方法:
VideoCapture(“rtspsrc location=\”rtsp://admin:admin666@192.168.1.106/h264/h264/main/av_stream\” latency=10 ! rtph264depay ! h264parse ! omxh264dec ! videoconvert ! appsink sync=false”)

3>调用tegra_multimedia解码
这是nvidia自带的编解码框架 目前用官方的解码1080p的h264可以做到解码速度达到150fps
接入rtsp的相机流
解决方法:ffmpeg/live555解析之后做个数据拷贝
我在看nvidia官方的demo,了解清楚数据结构之后就可以对接解析好的h264视频流(后续更新)
4>视频推流
需求:将处理后的opencv数据进行推流到网页
解决方案:
A> 用tegra_multimedia编码数据为h264, live555推流为rtsp
B> 直接用gstreamer推流(gstreamer自带rtsp的部分)
目前正在尝试用B方案解决(后续更新)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gstreamer 概述以及TX1 硬解码多路RTSP流 的相关文章

  • 如何提取到网页上播放的视频

    1 在播放视频页面的空白处点击右键 xff0c 会弹出菜单 xff0c 在菜单中选择审查元素 2 弹出的页面如图所示 xff0c 我们点击第二个network 3 利用size这一栏点击一下他就会按照文件大小就可以查找到视频 xff0c 一
  • Nodejs如何调用Dll模块

    苏格团队作者 xff1a Tomey 一 为什么需要用node js调用dll xff1f 公司项目采用Electron xff08 electronjs org xff09 开发pc应用 xff0c 会涉及到与底层硬件设备的通信 xff0
  • PyShark入门(1):简介

    原文地址 xff1a http zodiacg net 2016 07 in 本系列文章译自thePacketGeek的系列文章 原创翻译 xff0c 转载请注明出处 文章作者以PyShark为基础开发了Cloud Pcap xff0c 一
  • 将元素添加到List集合的第一位

    2019独角兽企业重金招聘Python工程师标准 gt gt gt list add 1 object 看一下add方法的注释 Inserts the specified element at the specified position
  • KMS激活时,常见的错误及处理

    0x80072EFE 出现这种是错误因为服务器响应超时或与服务器连接已丢失 xff0c 网络连接的问题 解决方法 xff1a 暂时关闭防火墙和杀毒软件 xff0c 将网卡禁用再启用 xff0c 确保网络是通的 0x8007232B 过 30
  • 精彩网页

    新华网美国频道 xff1a http us xinhuanet com php加密网址 xff1a https www phpjiami com 转载于 https www cnblogs com net5x p 6570727 html
  • java集合类(二)List学习

    接上篇 java集合类 xff08 一 xff09 List接口继承了Collection接口和Iterable接口 xff0c 即同样含有Collection和 Iterable的特性 xff0c 还有方法 xff0c 其基本方法有 xf
  • 构建安全的数据访问-部署注意事项(九)

    部署注意事项 以安全方式设计和开发的数据访问组件如果不以安全的方式进行部署 xff0c 仍然容易受到攻击 常见的部署做法是使数据访问代码和数据库驻留在单独的服务器上 这些服务器通常由内部防火墙隔开 xff0c 这就引进了额外的部署注意事项
  • matlab练习程序(随机直线采样)

    我只是感觉好玩 xff0c 写了这样一段程序 原理就是先随机生成两个点 xff0c 然后根据这两个点画直线 xff0c 最后在直线上的像素保留 xff0c 没在直线上的像素丢弃就行了 最后生成了一幅含有很多空洞的图像 当然 xff0c 对含
  • 跳一跳j算法ava代码_麻将游戏算法深入解析及实现代码

    麻将游戏算法深入解析及实现代码 这两天为了工具箱的完善 xff0c 整理了这些年引擎开发的一些资料 xff0c 无意中发现06年写的一个麻将算法 xff0c 编译运行了一下 xff0c 还是有点意思的 xff0c 拿出来整理一下分享给大家
  • 使用SP Racing F3飞控&ROSflight软件包的无人机自主飞行系统

    搭建四旋翼系统 机架 xff1a XR215 Plus 328 分线板 xff1a XR215 Plus PDB 飞控 xff1a SP Racing F3 标准版 xff08 Acro xff09 86 电机 xff1a 银燕RS2205
  • PostMan使用手册

    Postman 使用手册系列教程 xff1a Postman软件安装 Postman使用手册1 导入导出和发送请求查看响应 Postman使用手册2 管理收藏 Postman使用手册3 环境变量 Postman使用手册4 API test
  • python 网站文件及数据库备份脚本

    初学python xff0c 试着写了一份python网站文件备份和数据库备份的脚本 xff0c 功能是写出来了 xff0c 但感觉还是不太适应 xff0c 写得不太好 xff0c 以后还要努力哈 xff01 backup py读取back
  • 八款值得尝试的精美的 Linux 发行版(2017 版)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 在这篇文章中 xff0c 将会列出让一些令 Linux 用户印象最深刻且精美的 Linux 发行版 xff0c 包括对初学者友好和流行的发行版 1 elementary O
  • 大型网站架构系列:20本技术书籍推荐

    学习是技术人员成长的基础 xff0c 本次分享20本技术方面的书籍 xff0c 这些书不是每一本都是经典 xff0c 但是每一本都有其特点 以下20本大部分本人都看过 xff0c 因此推荐给大家 xff08 本次推荐的20本只是一个参考 x
  • 正确判断无人机指向故障 让电子罗盘远离磁干扰

    电子罗盘作为无人机产品的重要组成部件 xff0c 承载着为无人机引导绝对方位的功能 对于普通设计者而言 xff0c 经常会遇到电子罗盘校正困难 xff0c 校正需求过于频繁 xff0c 动态 高速运行时突发偏离 xff0c 以及无论怎么校正
  • 测试计划驱动开发模式 TPDD:一种比 TDD 更友好的开发模式

    相信大部分开发团队都在使用TDD xff0c 并且还有很多开发团队都 对外声明 在使用 TDD 开发模式 之所以说是 对外声明 xff0c 是因为很多开发团队虽然号称使用的是 TDD 开发模式 xff0c 实际开发过程中却无法满足 TDD
  • Android Button悬浮在SurfaceView上

    实现Button悬浮于与SurfaceView之上实现 注意 xff1a 你实现的SurfaceView和android中的Button EditView是同级的 xff0c 不能把一个包含在另一个里面 1 创建自己的SurfaceView
  • 异域linux内核漏洞,Ubuntu本地提权攻击漏洞复现(CVE-2017-16995)

    Ubuntu 16 04 4 kernel priv esc all credits to 64 bleidl vnik Tested on 4 4 0 116 generic 140 Ubuntu SMP Mon Feb 12 21 23
  • ROS 自定义消息类型

    引言 学习ROS的过程中 xff0c 在话题的发布与订阅之间 xff0c 我一直在思考 xff0c 我们能不能定义自己的话题名 xff0c 甚至在编写我们自己的ROS软件包时 xff0c 定义我们自己的消息类型 首先能不能定义自己的话题名

随机推荐