【GStreamer】gstreamer在linux下的简单使用

1.Linux下载与编译gstreamer

对于Ubuntu系统,使用如下命令可以下载gstreamer(Installing on Linux

sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

当然也可以自己编译源码,需要使用meson工具(Building from source using Meson),gstreamer的源码可以在github上找到(gstreamer),不过直接用命令行安装方便很多

在下载gstreamer之后,如果想要编译自己编写的代码,需要依赖gcc的支持,例如

gcc test.c -o test $(pkg-config --cflags --libs gstreamer-1.0)

这里的pkg-config描述了gcc过程中需要的gstreamer文件的链接,使用pkg-config --cflags --libs gstreamer-1.0可以打印链接信息,会输出gstreamer文件地址和glib的地址

lighthouse@VM-12-10-ubuntu:~$ pkg-config --cflags --libs gstreamer-1.0
-I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -pthread -I/usr/include/x86_64-linux-gnu -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0

2.gstreamer命令行工具的简单使用

gstreamer支持简单的命令行使用,安装之后提供3种工具可以快捷使用:

  • gst-inspect-1.0
    gst-inspect-1.0是一个工具,它可以打印出有关可用的 GStreamer 插件、特定插件或特定元素的信息
  • gst-launch-1.0
    gst-launch-1.0 是一个构建和运行基本 GStreamer 流水线的工具,例如可以使用这个工具快速的播放一个视频(这类似于FFmpeg中的ffplay.exe)
  • ges-launch-1.0
    ges-launch-1.0 是 GStreamer Editing Services (GES) 的一个工具,可以创建一个多媒体时间线,能够灵活的处理时间问题(该工具也可以快速播放视频)

(1)gst-inspect-1.0
使用gst-inspect-1.0打印gstreamer中注册的插件和元素的列表。下列输出中的第一列为插件库名称(如1394、a52dec和aasink等),第二列为该元素提供的具体元素名称(例如dv1394src、a52dec和aatv等),第三列为这些元素的简短描述,说明了元素功能,例如 Firewire (1394) DV video source 表示dv1394src是一个从Firewire(IEEE 1394)获取DV视频信号的源元素

lighthouse@VM-12-10-ubuntu:~$ gst-inspect-1.0
1394:  dv1394src: Firewire (1394) DV video source
1394:  hdv1394src: Firewire (1394) HDV video source
a52dec:  a52dec: ATSC A/52 audio decoder
aasink:  aasink: ASCII art video sink
aasink:  aatv: aaTV effect
accurip:  accurip: AccurateRip(TM) CRC element
adaptivedemux2:  dashdemux2: DASH Demuxer
adaptivedemux2:  hlsdemux2: HLS Demuxer
adaptivedemux2:  mssdemux2: Smooth Streaming demuxer (v2)
adder:  adder: Adder
adpcmdec:  adpcmdec: ADPCM decoder
adpcmenc:  adpcmenc: ADPCM encoder
aes:  aesdec: aesdec
aes:  aesenc: aesenc
aiff:  aiffmux: AIFF audio muxer
aiff:  aiffparse: AIFF audio demuxer
alaw:  alawdec: A Law audio decoder
alaw:  alawenc: A Law audio encoder
alpha:  alpha: Alpha filter
alphacolor:  alphacolor: Alpha color filter
alsa:  alsadeviceprovider (GstDeviceProviderFactory)
alsa:  alsamidisrc: AlsaMidi Source
.... 后续省略

(2)gst-launch-1.0
使用gst-launch-1.0可以快速播放.webm格式的视频,如下所示

gst-launch-1.0 playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm

其中playbin是gstreamer中的一个预先构建的高级元素,内部集成了多个其他元素,如uridecodebin、decodebin等,如果用户在CLI中指定了这个元素,gstreamer会自动构建一条适合输入uri的一条流水线,实现解码和渲染的功能。尽管playbin内部包含了构建流水线所需的许多组件,但它本身仍然是一个单一的元素。播放webm的效果为
在这里插入图片描述
也可以使用gst-launch-1.0播放yuv、mp4和h264格式的视频,如下所示
yuv格式

// blocksize = 832x480x1.5 = 599040,表示一帧的大小(缓冲区大小为一帧)
gst-launch-1.0 filesrc blocksize=599040 location=/home/ubuntu/Desktop/workspace/BasketballDrill_832x480_50.yuv ! queue ! "video/x-raw, format=I420, width=832, height=480, framerate=50/1" !  rawvideoparse use-sink-caps=true ! autovideosink
// 对比之下ffplay要简单不少
ffplay -f rawvideo -video_size 832x480 -pixel_format yuv420p /home/ubuntu/Desktop/workspace/BasketballDrill_832x480_50.yuv

在这里插入图片描述

mp4格式和h264格式

// mp4格式
gst-launch-1.0 playbin uri=file:///home/ubuntu/Desktop/workspace/Crew.mp4
// h264格式
gst-launch-1.0 filesrc location=/home/ubuntu/Desktop/workspace/Crew.h264 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw,width=1280,height=720 ! autovideosink

在这里插入图片描述
(3)ges-launch-1.0
ges-launch-1.0可以创建一个多媒体时间线,能够灵活的处理时间问题。可以加载现有文件的时间线,或者根据指定的命令创建一个新的时间线,最简单的命令是

ges-launch-1.0 +clip test.webm

也可以指定开始播放的时间点

ges-launch-1.0 +clip test.webm inpoint=4.0

3.基于gstreamer的webm视频播放器

gstreamer允许用户按照需求,自定义媒体通道(如videosink、audiosink、demux等),实现特定的功能。使用GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()这个宏能够输出当前的graph,输出的文件格式为dot类型,再使用Graphviz工具将dot文件转换成为png文件,就可以查阅了。这个宏的简单用法是

// arg1@: GST_BIN(pipeline),通常会将pipeline定义为GstElement类型,这里转换成GstBin类型,这是允许的,因为GstElement是GstBin的父类
// arg2@: GST_DEBUG_GRAPH_SHOW_ALL,打印所有信息
// arg3@: pipeline,表示输出文件的名称
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline")

参考gstreamer/subprojects/gst-docs/example/tutorial/basic-tutorial-3.c,将音频输出改成视频输出。为了打印其graph,需要在gst_init()初始化gst之前,设置dot文件的存储路径(GST_DEBUG_DUMP_DOT_DIR)。使用视频输出的代码如下所示,主要不同点在于将获取的音频元素修改成了视频元素

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

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *videoconvert;
  GstElement *videoscale;
  GstElement *videosink;
} CustomData;

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;
  
  // 设置dot文件存储路径
  g_setenv("GST_DEBUG_DUMP_DOT_DIR", "/home/ubuntu/Desktop/workspace", TRUE);
  const gchar* dot_dir = g_getenv("GST_DEBUG_DUMP_DOT_DIR");
  printf("dot_dir:%s\n", dot_dir);

  /* Initialize GStreamer */
  // 初始化gstreamer
  gst_init (&argc, &argv);

  /* Create the elements */
  // 这里修改成获取视频元素
  data.source = gst_element_factory_make ("uridecodebin", "source");
  data.videoconvert = gst_element_factory_make ("videoconvert", "videoconvert");
  data.videoscale = gst_element_factory_make ("videoscale", "videoscale");
  data.videosink = gst_element_factory_make ("autovideosink", "videosink");

  /* Create the empty pipeline */
  // 创建空的pipeline
  data.pipeline = gst_pipeline_new ("test-pipeline");

  if (!data.pipeline || !data.source || !data.videoconvert || !data.videoscale || !data.videosink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  // 将pipeline, source, videoconvert, videoscale, videosink添加到同一个bin当中
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.videoconvert, data.videoscale, data.videosink, NULL);
  // 将videoconvert, videoscale, videosink链接到一起
  if (!gst_element_link_many (data.videoconvert, data.videoscale, data.videosink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Set the URI to play */
  // 设置想要播放的URI路径,这里也可以改成本地,如file:///home/ubuntu/Desktop/workspace/video.webm
  g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

  /* Connect to the pad-added signal */
  // 将pad-added这个信号与source链接,如果source收到外部输入,会激活这个信号,随后调用
  // pad_added_handler这个回调函数
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

  /* Start playing */
  // 将pipeline的状态设置为GST_STATE_PLAYING,表示这条媒体通道开始播放
  ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Listen to the bus */
  // 创建pipeline上的总线
  bus = gst_element_get_bus (data.pipeline);
  do {
    // 监听pipeline上的总线消息
    // GST_CLOCK_TIME_NONE表示无限制时间,即会一直阻塞进程,直到总线上有新消息出现
    // GST_MESSAGE_STATE_CHANGE,GST_MESSAGE_ERROR,GST_MESSAGE_EOS为三种消息类型,这里表示只过滤这三种消息
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* Parse message */
    if (msg != NULL) {
      GError *err;
      gchar *debug_info;

      switch (GST_MESSAGE_TYPE (msg)) {
        case GST_MESSAGE_ERROR:
          // 检测总线上出现了错误消息
          gst_message_parse_error (msg, &err, &debug_info);
          g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
          g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
          g_clear_error (&err);
          g_free (debug_info);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          // 到达流的结尾
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          // pipeline的状态发生了变化
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

  /* Free resources */
  // 释放资源
  gst_object_unref (bus);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  gst_object_unref (data.pipeline);
  return 0;
}

/* This function will be called by the pad-added signal */
// 回调函数,当这个函数被调用时,会创建一个新的pad,用以连接source和videoconvert
// 其中,source的pad为发送端(src),videoconvert的pad为接收端(sink)
// 输入参数中的new_pad就是source中的pad
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  // 从videoconvert中获取sink这个默认的pad
  GstPad *sink_pad = gst_element_get_static_pad (data->videoconvert, "sink");
  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));

  /* If our converter is already linked, we have nothing to do here */
  // 检查当前的sink pad是否已经链接上了
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  // 检查new pad类型
  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);
  if (!g_str_has_prefix (new_pad_type, "video/x-raw")) {
    g_print ("It has type '%s' which is not raw video. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  // 尝试连接两个pad
  ret = gst_pad_link (new_pad, sink_pad);
  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);
  }
  // 输出dot文件
  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline_graph");
exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

编译运行

gcc basic-tutorial-3.c -o basic_3 $(pkg-config --cflags --libs gstreamer-1.0)
./basic_3

输出的视图框和前面命令行的一致
在这里插入图片描述
同时,在对应路径下会找到对应的dot文件,我这里是0.00.00.015909702-pipeline_graph.dot,转换成png图片

dot -Tpng -o graph_pipeline.png 0.00.00.015909702-pipeline_graph.dot

在这里插入图片描述
需要注意的是,在pad_added_handler()中存储dot文件,因为该函数被调用时,才会执行数据流传输,graph才完整,如果在tutorial_main()中存储dot文件,会缺少前面接收远端数据流的元素,是不完整的。从图中看,一共有4个元素,分别是:

  1. GstURIDecodeBin中的source,用于接收远端数据(如网络URL),能够自动选择合适的解码器
    source元素中又包含了很多子元素,其中:
    (1)GstSoupHTTPSrc中的source,用于从指定的URI远程读取数据
    (2)GstTypeFindElement typefindelement0,用于确定媒体流的类型。通过分析流中的数据,尝试识别媒体类型(如音频、视频),并设置相应的媒体类型信息(caps)
    (3)GstQueue2 queue2-0,用于在管道中存储和转发数据
    (4)GstDecodeBin中的decodebin0,用于解码媒体流,能够自动选择合适的解码器进行解码
     (a)GstTypeFindElement typefind,用于确定媒体流类型
     (b)GstMatroskaDemux matroskademux0,用于将Matroska容器格式(如.mkv)解复用成单独的音视频流,将容器中的数据流分离成可以被进一步处理的音视频轨道
     (c)GstMultiQueue multiqueue0,多输入队列元素,可以接收多个输入流,并将它们合并为一个输出流
     (d)GstVorbisDec vorbisdec0,用于解码Vorbis编码的音频流
     (e)GstVP8Dec vp8dec0,用于解码VP8视频流

GstURIDecodeBin中的数据流向是:
(1)GstSoupHTTPSrc中的source从远端location(https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm)拉取数据。图中的src端口是发送端口,">"符号表示"playing"状态,“bfb”符号代表三种状态,第一个"b"表示远端接收过来的数据是否阻塞,"f"表示是否清空缓存,第二个"b"表示当前模块输出的数据是否阻塞,这三个状态的flag如果为大写(如"bFb"表示正在flushing),表示有效,"T"符号表示任务已经开始("t"符号表示任务暂停)。图中表示source模块正在启用,获取远端数据并且向后续模块传输数据
(2)GstTypeFindElement typefindelement0解析数据流,标识caps=video/webm,即数据类型为视频中的webm格式
(3)GstQueue2 queue2-0对数据流进行缓冲处理,确保数据流的平滑处理,max-size-buffers=0表示不限制缓冲区的数量,队列可以无限长;use-buffering=TRUE,GstQueue2会根据low-watermark和high-watermark属性的值来控制缓冲区的填充率,并在缓冲区的填充水平低于或高于阈值时发出GST_MESSAGE_BUFFERING消息;avg-in-rate=1548424,表示平均输入速率,单位是字节/秒,用于估算数据流入队列的平均速度;bitrate=1538934,用于限制队列的大小,影响如何根据比特率计算队列中数据的时间长度
(4)GstDecodeBin中的decodebin0,用于解码媒体流
 (a)GstTypeFindElement typefind,确定媒体流类型为caps=video/webm
 (b)GstMatroskaDemux matroskademux0,将前面获取的媒体数据解复用,分为audio和video两个部分,其中audio为vorbis格式(channel=2,rate=48000),video为vp8格式(width=854,height=480,帧率未设置)
 (c)GstMultiQueue multiqueue0,获取前面的媒体数据,合并为一个数据流(这里没有合并)。current-level-buffers表示队列中当前的缓冲区数量,current-level-bytes表示队列中当前的数据量(单位为字节)
 (d)GstVorbisDec vorbisdec0,接收音频数据,传输到proxypad5_0x70a5fc077430,这里没有后续的使用,应该是一个虚拟的pad
 (e)GstVP8Dec vp8dec0,接收视频数据,格式为format=I420,width=854,height=480,interlace-mode=progressive,framerate=0/1(没有设置),输出数据到proxypad4这个端口,这个端口会作为decodebin0的输出端口src_0,输出到后续的模块

  1. GstVideoConvert中的videoconvert,用于数据格式转换
    videoconvert常用于格式转换,如果没有特定的要求,该模块不会修改格式。在dot图中,打印了videoconvert支持的能力集,video/x-raw format ; {(string)A444_16LE, …}, …, video/x-raw(ANY) format : {(string) DMA_DRM, …}。图中的qos=TRUE,表示videoconvert会根据下游元素的反馈信息调整自己的处理速度和输出质量,以优化整个管道的Qos表现。

  2. GstVideoScale中的videoscale,用于改变视频数据的尺寸,调整视频的宽高
    videoscale用于调整视频宽高,如果没有特定的要求,该模块不会修改格式。

  3. GstAutoVideoSink中的videosink,用于播放数据,自动选择合适的视频输出设备,将视频帧渲染到屏幕
    (1)GstGLUploadElement gluploadelement0,将CPU内存中的图像数据上传到GPU内存中,以便后续OpenGL的处理,这里不会改变图像数据内容,只是修改了内存域
    (2)GstColorConvertElement glcolorconvertelement0,使用OpenGL来处理视频格式和颜色空间,例如从YUV转换到RGB
    (3)GstGLColorBalance glcolorbalance0,调整视频流的亮度、对比度、色调和饱和度
    (4)GstGLImageSink sink,基于OpenGL或OpenGL ES的视频输出元素,实现了VideoOverlay接口,实现输出视频的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值