背景

业务需要自己做解码,因为软解码CPU占有率太高,需要硬件加速,也就是硬解码。可以使用ffmpeg或者Gstreamer进行解码,我们选择用Gstreamer做解码。

系统环境:Ubuntu 20.04
代码功能:实现rtsp流的H264硬解码,获取解码后的数据;

一、Gstreamer介绍

Gstreamer是一个用于开发流媒体应用的开源框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的元素(Element),并且能够很方便地安装到任意一个管道上。由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件“组装”出一个功能完善的流媒体应用程序。

二、Gstreamer基础概念

2.1 Element

从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。

一个Element实现了一个功能(如读取文件,解码,输出等),一个程序需要创建多个element,并按顺序将其串连起来,构成一个完整的pipeline

按照各自功能上的差异,GstElement 可以细分成如下几类:

  • Source Element 数据源元件,只有输出端。它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元件的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。

  • Filter Element 过滤器元件,既有输入端又有输出端。它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。 一个典型的过滤器元件的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用)

  • Sink Element 接收器元件,只有输入端。它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元件的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。

若干elemetns组成一个pipeline
在这里插入图片描述

三、Gstreamer编解码一般步骤

  1. 创建所需elements
  2. gst_bin_add_many添加创建的elements
  3. 链接elements
  4. 设置pipeline为Playing状态
  5. 设置采样回调

三、完整代码示例

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

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;
}
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐