业务需要自己做解码,因为软解码CPU占有率太高,需要硬件加速,也就是硬解码。可以使用ffmpeg或者Gstreamer进行解码,我们选择用Gstreamer做解码。
系统环境:Ubuntu 20.04
代码功能:实现rtsp流的H264硬解码,获取解码后的数据;
Gstreamer是一个用于开发流媒体应用的开源框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的元素(Element),并且能够很方便地安装到任意一个管道上。由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件“组装”出一个功能完善的流媒体应用程序。
从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。
一个Element实现了一个功能(如读取文件,解码,输出等),一个程序需要创建多个element,并按顺序将其串连起来,构成一个完整的pipeline
按照各自功能上的差异,GstElement 可以细分成如下几类:
Source Element 数据源元件,只有输出端。它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元件的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。
Filter Element 过滤器元件,既有输入端又有输出端。它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。 一个典型的过滤器元件的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用)
Sink Element 接收器元件,只有输入端。它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元件的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。
#include
#include
#include
typedef struct _custom_data
{
/* pipeline{source->rtpdepay->rtp_queue->h264parse->hard_decoder->cudaconvert->cudadowload->video_queue->sink} */
GstElement *pipeline,
*source, *rtpdepay, *rtp_queue, *h264parse, *h264_queue,
*hard_decoder, *img_filter, *cudaconvert, *cudadowload, *video_queue, *sink;
std::string Rtsp;
} CustomData;
/* Create the elements */
GstFlowReturn InitGst(CustomData *data)
{
GstFlowReturn retVal;
/* Create the elements */
data->pipeline = gst_pipeline_new("rtsp-decode-pipeline");
/*
* param1: element 类型 param2: 名称
* gst_element_factory_make(param1, param2)
*/
data->source = gst_element_factory_make("rtspsrc", "source");
// rtph264depay: Extracts H264 video from RTP packets (RFC 3984)
data->rtpdepay = gst_element_factory_make("rtph264depay", "rtpdepay");
data->rtp_queue = gst_element_factory_make("queue", "rtp_queue");
// h264parse: Parses H.264 streams
data->h264parse = gst_element_factory_make("h264parse", "h264parse");
data->h264_queue = gst_element_factory_make("queue", "h264_queue");
// nvh264dec: NVDEC video decoder
data->hard_decoder = gst_element_factory_make("nvh264dec", "hard_decoder"); // 硬解
// cudaconvert: Convert video frames between supported video formats.
data->cudaconvert = gst_element_factory_make("cudaconvert", "cudaconvert");
// cudadownload: Downloads data from NVIDA GPU via CUDA APIs
data->cudadowload = gst_element_factory_make("cudadownload", "cudadownload");
if (!data->hard_decoder || !data->cudaconvert || !data->cudadowload)
{
data->hard_decoder = gst_element_factory_make("avdec_h264", "hard_decoder"); // 软解
data->cudaconvert = gst_element_factory_make("videoconvert", "cudaconvert");
data->cudadowload = gst_element_factory_make("queue", "cudadownload");
/* https://www.nvidia.cn/Download/index.aspx?lang=cn 驱动下载地址*/
g_print("Use SoftDecoder, Ensure NVIDIA driver is the lastest version!!!\n");
}
else
{
g_print("Use HardDecode!\n");
}
data->video_queue = gst_element_factory_make("queue", "video_queue");
data->sink = gst_element_factory_make("appsink", "sink"); // autovideosink, fakesink, appsink, filesink
if (!data->pipeline || !data->source || !data->rtp_queue || !data->rtpdepay || !data->h264_queue ||
!data->h264parse || !data->hard_decoder || !data->cudaconvert || !data->cudadowload || !data->video_queue || !data->sink)
{
g_print("Not all elements could be created!!!\n");
return GST_FLOW_ERROR;
}
g_print("All elements created success\n");
/* 设置rtsp的输入地址 */
g_object_set(G_OBJECT(data->source), "location", data->Rtsp.c_str(), "latency", 2000, NULL);
/* 设置输出格式 */
g_object_set(G_OBJECT(data->sink),
"sync", FALSE,
"emit-signals", TRUE,
"caps", gst_caps_new_simple("video/x-raw",
// "width", G_TYPE_INT, 1920,
// "height", G_TYPE_INT, 1080,
// "framerate", GST_TYPE_FRACTION, 10, 1,
"format", G_TYPE_STRING, "NV12", NULL),
NULL);
/* 创建pipeline,注意此时各个组件还没有连接,只是add到管道,也就是说,add要在link之前 */
gst_bin_add_many(GST_BIN(data->pipeline), data->source, data->rtpdepay, data->rtp_queue, data->h264parse, data->h264_queue, data->hard_decoder, data->cudaconvert, data->cudadowload, data->video_queue, data->sink, NULL);
// 此时rtsp src和 rtph264depay还没有连接,所以必须设置pad-added信号监听,
// 在管道开始工作后,确定了数据格式,再把它们连接起来
g_signal_connect(data->source, "pad-added", G_CALLBACK(RtspSrcPadAdded_callback), data);
// 连接元素,注意顺序不能错,因为还没有确定数据的格式,所以此时rtsp src和rtph264depay还没建立连接,
// 先将rtsp src之后的元件连接起来
if (!gst_element_link_many(data->rtpdepay, data->rtp_queue, data->h264parse, data->hard_decoder, data->cudaconvert, data->cudadowload, data->video_queue, NULL))
{
g_printerr("@@@ OpenRtsp: Failed to link rtpdepay -> video_queue\n");
return GST_FLOW_ERROR;
}
if (!gst_element_link_many(data->video_queue, data->sink, NULL))
{
g_printerr("@@@ OpenRtsp: Failed to link video_queue -> sink\n");
return GST_FLOW_ERROR;
}
g_print("All elements Link success\n");
/* 设置 pipeline 状态为 Playing */
GstStateChangeReturn ret = gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE)
{
g_printerr("@@@ OpenRtsp: Unable to set the pipeline to the playing state.\n");
return GST_FLOW_ERROR;
}
// 设置采样回调
g_signal_connect(data->sink, "new-sample", G_CALLBACK(ReadvideoFrame_callback), data);
return GST_FLOW_OK;
}
/* source new src pad create */
static void RtspSrcPadAdded_callback(GstElement *src, GstPad *new_pad, gpointer user_data)
{
CustomData *data = (CustomData *)user_data;
GstPad *sink_pad = gst_element_get_static_pad(data->rtpdepay, "sink");
GstCaps *p_caps;
gchar *description;
GstPadLinkReturn ret;
GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL;
g_print("Received new pad '%s' from '%s':\n", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(src));
//使用gst_pad_is_linked(sink_pad)来检查是否已经连接好了,若是则忽略该信号
if (gst_pad_is_linked(sink_pad))
{
g_print("We are already linked. Ignoring.\n");
goto exit;
}
// here, you would setup a new pad link for the newly created pad
// so, now find that rtph264depay is needed and link them?
p_caps = gst_pad_get_pad_template_caps(new_pad);
description = gst_caps_to_string(p_caps);
g_print("new pad caps: %s\n", description);
g_free(description);
if (NULL != p_caps)
gst_caps_unref(p_caps);
/* Attempt the link */
/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps(new_pad);
new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
new_pad_type = gst_structure_get_name(new_pad_struct);
//因为rtph264depay要求application/x-rtp格式的输入数据,所以找rtspsrc的pad里面有没有这种格式的数据
if (!g_str_has_prefix(new_pad_type, "application/x-rtp"))
{
g_print("It has type '%s' which is not application/x-rtp. Ignoring.\n", new_pad_type);
goto exit;
}
//如果找到了application/x-rtp格式的输入数据,则将rtspsrc的source pad和rtph264depay的sink pad连起来
ret = gst_pad_link(new_pad, sink_pad); // link
if (GST_PAD_LINK_FAILED(ret))
{
g_print("Type is '%s' but link failed.\n", new_pad_type);
}
else
{
g_print("Link succeeded (type '%s').\n", new_pad_type);
}
/* 解引用,释放资源*/
if (NULL != new_pad_caps)
gst_caps_unref(p_caps);
exit:
/* 解引用,释放资源*/
if (sink_pad != NULL)
gst_object_unref(sink_pad);
}
/* 每次从视频流中获取一帧数据 */
void ReadVideoFrame_callback(GstElement *sink, gpointer user_data)
{
CustomData *data = (CustomData *)user_data;
GstSample *sample = nullptr;
gsize data_size = 0;
gsize stream_size = 0;
// 使用pull-sample拉取视频帧,并映射到map变量,通过map拷贝出frame数据
g_signal_emit_by_name(sink, "pull-sample", &sample);
if (sample){
GstBuffer *buffer = gst_sample_get_buffer(sample);
if (buffer){
GstMapInfo map;
if (gst_buffer_map(buffer, &map, GST_MAP_READ)){
char buf[4096] = {0};
memcpy(buf, map.data, data_size); // 获取解码后的数据到buf
// release buffer mapping
gst_buffer_unmap(buffer, &map);
}else{
printf("fgst_buffer_map error failed!!!\n");
}
}else{
printf("gst_sample_get_buffer failed!!!\n");
}
gst_sample_unref(sample); // release sample reference
}else{
printf("sample is null...\n");
}
return;
}
int main()
{
GstFlowReturn ret;
CustomData *context = (CustomData*)malloc(sizeof(CustomData));
if (!context){
printf("context malloc failed!\n");
return -1;
}
context->Rtsp = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
if ((ret = InitGst(context)) == GST_FLOW_OK){
printf("InitGst success..\n");
}
while(1);
return 0;
}