热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

以NVIDIATX1为例硬解码就是利用硬件芯片来解码的,TX1有单独的解码模块,NVDEC.软解码是用软件程序来解码,比较占用CPU资源查

以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
#include 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&#61;-1 ); // use onboard camera by default (>&#61;0 for V4L2)static gstCamera* Create( uint32_t width, uint32_t height, int v4l2_device&#61;-1 );// 析构函数~gstCamera();// 开始和停止流bool Open();void Close();// 采集YUV(NV12格式)bool Capture( void** cpu, void** cuda, unsigned long timeout&#61;ULONG_MAX );// 抓取YUV-NV12 CUDA image, 转换成 float4 RGBA (像素范围在 0-255)// 转换如果在CPU上进行&#xff0c;设置zeroCopy&#61;true,默认只在CUDA上.bool ConvertRGBA( void* input, void** output, bool zeroCopy&#61;false );// 图像大小信息 inline(内联函数&#xff0c;适合简单的函数)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; }// 默认图像大小&#xff0c;可以在create时改变static const uint32_t DefaultWidth &#61; 1280;static const uint32_t DefaultHeight &#61; 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&#61;"rtspsrc location&#61;rtsp://admin:buaa123456&#64;192.168.1.106:554/h264/ch1/main/av_stream latency&#61;0 ! queue ! rtph264depay ! h264parse ! queue ! omxh264dec ! appsink name&#61;mysink";uint32_t mWidth;uint32_t mHeight;uint32_t mDepth;uint32_t mSize;static const uint32_t NUM_RINGBUFFERS &#61; 16;//环形队列来解决数据阻塞问题void* mRingbufferCPU[NUM_RINGBUFFERS];void* mRingbufferGPU[NUM_RINGBUFFERS];QWaitCondition* mWaitEvent;//mutex.lock() //锁住互斥量&#xff08;mutex&#xff09;。如果互斥量是解锁的&#xff0c;那么当前线程就立即占用并锁定它。否则&#xff0c;当前线程就会被阻塞&#xff0c;知道掌握这个互斥量的线程对它解锁为止。//mutex.unlock()//解锁//mutex.tryLock()//尝试解锁&#xff0c;如果该互斥量已经锁住&#xff0c;它就会立即返回QMutex* mWaitMutex;QMutex* mRingMutex;uint32_t mLatestRGBA;uint32_t mLatestRingbuffer;bool mLatestRetrieved;void* mRGBA[NUM_RINGBUFFERS];int mV4L2Device; // -1 for onboard, >&#61;0 for V4L2 deviceinline bool onboardCamera() const { return (mV4L2Device <0); }
};#endifbool gstCamera::Capture( void** cpu, void** cuda, unsigned long timeout )
{/*wait() 函数必须传入一个已上锁的 mutex 对象&#xff0c;在 wait() 执行过程中&#xff0c;mutex一直保持上锁状态&#xff0c;直到调用操作系统的wait_block 在阻塞的一瞬间把 mutex 解锁&#xff08;严格说来应该是原子操作&#xff0c;即系统能保证在真正执行阻塞等待指令时才解锁&#xff09;。另一线程唤醒后&#xff0c;wait() 函数将在第一时间重新给 mutex 上锁&#xff08;这种操作也是原子的&#xff09;&#xff0c;直到显示调用 mutex.unlock() 解锁。*/mWaitMutex->lock();
const bool wait_result &#61; mWaitEvent->wait(mWaitMutex, timeout);
mWaitMutex->unlock();if( !wait_result )return false;mRingMutex->lock();const uint32_t latest &#61; mLatestRingbuffer;const bool retrieved &#61; mLatestRetrieved;mLatestRetrieved &#61; true;mRingMutex->unlock();// skip if it was already retrievedif( retrieved )return false;if( cpu !&#61; NULL )*cpu &#61; mRingbufferCPU[latest];if( cuda !&#61; NULL )*cuda &#61; mRingbufferGPU[latest];return true;
}#define release_return { gst_sample_unref(gstSample); return; }// checkBuffer
void gstCamera::checkBuffer()
{bool write_flags&#61;true;//默认写数据if( !mAppSink )return;// block waiting for the buffer 函数被唤醒until A sample or EOS 可用 或者appsink 被设置成 ready/null stateGstSample* gstSample &#61; 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 gstSampleGstBuffer* gstBuffer &#61; gst_sample_get_buffer(gstSample);if( !gstBuffer ){printf(LOG_GSTREAMER "gstreamer camera -- gst_sample_get_buffer() returned NULL...\n");return;}// retrieveGstMapInfo 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 &#61; map.data; //GST_BUFFER_DATA(gstBuffer);const uint32_t gstSize &#61; map.size; //GST_BUFFER_SIZE(gstBuffer);if( !gstData ){printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL data pointer...\n");release_return;}// 取出capsGstCaps* gstCaps &#61; gst_sample_get_caps(gstSample);if( !gstCaps ){printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL caps...\n");release_return;}GstStructure* gstCapsStruct &#61; 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 bufferint width &#61; 0;int height &#61; 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 &#61; width;mHeight &#61; height;mDepth &#61; (gstSize * 8) / (width * height);mSize &#61; gstSize;//printf(LOG_GSTREAMER "gstreamer camera recieved %ix%i frame (%u bytes, %u bpp)\n", width, height, gstSize, mDepth);// make sure ringbuffer is allocatedif( !mRingbufferCPU[0] ){for( uint32_t n&#61;0; n if( !cudaAllocMapped(&mRingbufferCPU[n], &mRingbufferGPU[n], gstSize) )printf(LOG_CUDA "gstreamer camera -- failed to allocate ringbuffer %u (size&#61;%u)\n", n, gstSize);}printf(LOG_CUDA "gstreamer camera -- allocated %u ringbuffers, %u bytes each\n", NUM_RINGBUFFERS, gstSize);}// copy to next ringbufferconst uint32_t nextRingbuffer &#61; (mLatestRingbuffer &#43; 1) % NUM_RINGBUFFERS; //printf(LOG_GSTREAMER "gstreamer camera -- using ringbuffer #%u for next frame\n", nextRingbuffer);memcpy(mRingbufferCPU[nextRingbuffer], gstData, gstSize);// FILE *fp&#61;fopen("out.yuv","w&#43;");// fwrite(gstData,gstSize,1,fp);// fclose(fp);//test h264 write//void *writedata&#61;map.data;// while(write_flags&#61;&#61;true)// {// FILE *fp&#61;fopen("out.264","a&#43;");// fwrite(writedata,gstSize,1,fp);// write_flags&#61;false;// fclose(fp);// }gst_buffer_unmap(gstBuffer, &map); //gst_buffer_unref(gstBuffer);gst_sample_unref(gstSample);// update and signal sleeping threadsmRingMutex->lock();mLatestRingbuffer &#61; nextRingbuffer;mLatestRetrieved &#61; false;mRingMutex->unlock();mWaitEvent->wakeAll();
}

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

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

A.安装gstreamer依赖

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

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


推荐阅读
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • Linux下安装免费杀毒软件ClamAV及使用方法
    本文介绍了在Linux系统下安装免费杀毒软件ClamAV的方法,并提供了使用该软件更新病毒库和进行病毒扫描的指令参数。同时还提供了官方安装文档和下载地址。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
  • POCOCLibraies属于功能广泛、轻量级别的开源框架库,它拥有媲美Boost库的功能以及较小的体积广泛应用在物联网平台、工业自动化等领域。POCOCLibrai ... [详细]
  • 一、死锁现象与递归锁进程也是有死锁的所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作 ... [详细]
author-avatar
曦雯天使
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有