一、简介
Application Development Manual
Gstreamer是一个支持Windows,Linux,Android, iOS的跨平台的多媒体框架,应用程序可以通过管道(Pipeline)的方式,将多媒体处理的各个步骤串联起来,达到预期的效果。每个步骤通过元素(Element)基于GObject对象系统通过插件(plugins)的方式实现,方便了各项功能的扩展。
GStreamer 的核心功能是为插件、数据流和媒体类型处理/协商提供框架。它还提供了一个 API 来使用各种插件编写应用程序。
GStreamer 提供
- 多媒体应用程序的API
- 插件架构
- 流水线架构
- 一种媒体类型处理/协商机制
- 同步机制
- 超过 250 个插件提供超过 1000 个Elements
- 一套工具
1.Gstreamer框架
-
Tools or Media Applications
最上面一层为应用,比如gstreamer自带的一些工具(gst-launch,gst-inspect等),以及基于gstreamer封装的库(gst-player,gst-rtsp-server,gst-editing-services等)根据不同场景实现的应用。
- gst-inspect-1.0:用于打印有关可用 GStreamer 插件的信息、有关特定插件的信息或有关特定元素的信息。
- gst-launch-1.0:是一种构建和运行GStreamer管道的工具,管道描述是由感叹号 (!) 分隔的元素列表。
- ges-launch-1.0:可以从现有项目加载时间线,或从指定的命令创建时间线。
-
Core Framework
主要提供:
- 上层应用所需接口
- Plugin的框架
- Pipline的框架
- 数据在各个Element间的传输及处理机制
- 多个媒体流(Streaming)间的同步(比如音视频同步)
- 其他各种所需的工具库
-
Plugins
最下层为各种插件,实现具体的数据处理及音视频输出,应用不需要关注插件的细节,会由Core Framework层负责插件的加载及管理。主要分类为:
- Protocols:负责各种协议的处理,file,http,rtsp等。
- Sources:负责数据源的处理,alsa,v4l2,tcp/udp等。
- Formats:负责媒体容器的处理,avi,mp4,ogg等。
- Codecs:负责媒体的编解码,mp3,vorbis等。
- Filters:负责媒体流的处理,converters,mixers,effects等。
- Sinks:负责媒体流输出到指定设备或目的地,alsa,xvideo,tcp/udp等。
Gstreamer框架根据各个模块的成熟度以及所使用的开源协议,将core及plugins置于不同的源码包中:
- gstreamer: 包含core framework及core elements。
- gst-plugins-base: gstreamer应用所需的必要插件。
- gst-plugins-good: 高质量的采用LGPL授权的插件。
- gst-plugins-ugly: 高质量,但使用了GPL等其他授权方式的库的插件,比如使用GPL的x264,x265。
- gst-plugins-bad: 质量有待提高的插件,成熟后可以移到good插件列表中。
- gst-libav: 对libav封装,使其能在gstreamer框架中使用。
2.Gstreamer基础概念
1)Element
Element是Gstreamer中最重要的对象类型之一。一个element实现一个功能(读取文件,解码,输出等),程序需要创建多个element,并按顺序将其串连起来(使用管道 !),构成一个完整的pipeline。
# Print all elements
gst-inspect-1.0 -a
# List the plugin contents
gst-inspect-1.0 --plugin
# 查看某个element
gst-inspect-1.0 videotestsrc
element主要有三种:
- source element,主要是作为一个pipeline中的source节点,是数据源。source element只有src pad,即只有输出口。
- sink element ,作为pipline的end节点 。sink element 只有sink pad,即只有输入口。
- common element,作为pipline中的中间数据处理单元,既有sink pad,也有src pad,通常有:Filters, convertors, demuxers, muxers and codecs等
- Filters:过滤器
- convertors:转化器
- demuxers:拆分器,将sink拆分为多个src
- muxers:合并器,将多个sink合并为一个src
- codecs:编解码器
2)Pad
pads 是元素与外界的接口,有两个属性定义:方向和可用性。两个element必须通过pad才能连接起来,在element通过pad连接成功后,数据会从上一个element的src pad传到下一个element的sink pad然后进行处理。一个element可以同时拥有多个相同的pad。
方向性:作为element的输入/输出接口,方向是从一个元素的src pad(生产数据)到另一个元素的sink pad(消费数据)的数据流
可用性:分always, sometimes 和 on request。
特性(Properties):pad支持的参数
-
能力(Capabilities):pad可以拥有多种能力,会在连接时通过比较src pad和sink pad中所支持的能力,来选择最恰当的数据类型用于传输,如果element不支持,程序会直接退出。
-
例如element videotestsrc定义如下
src video/x-raw: format: { AYUV64, ARGB64, GBRA_12LE, GBRA_12BE, Y412_LE, Y412_BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, RGB10A2_LE, BGR10A2_LE, Y410, GBRA, ABGR, VUYA, BGRA, AYUV, ARGB, RGBA, A420, AV12, Y444_16LE, Y444_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, v210, UYVP, I420_10LE, I420_10BE, P010_10LE, NV12_10LE32, NV12_10LE40, P010_10BE, Y444, RGBP, GBR, BGRP, NV24, xBGR, BGRx, xRGB, RGBx, BGR, IYU2, v308, RGB, Y42B, NV61, NV16, VYUY, UYVY, YVYU, YUY2, I420, YV12, NV21, NV12, NV12_64Z32, NV12_4L4, NV12_32L32, Y41B, IYU1, YVU9, YUV9, RGB16, BGR16, RGB15, BGR15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 } width: [ 1, 2147483647 ] height: [ 1, 2147483647 ] framerate: [ 0/1, 2147483647/1 ] multiview-mode: { (string)mono, (string)left, (string)right } video/x-bayer: format: { bggr, rggb, grbg, gbrg } width: [ 1, 2147483647 ] height: [ 1, 2147483647 ] framerate: [ 0/1, 2147483647/1 ] multiview-mode: { (string)mono, (string)left, (string)right }
-
因此可以用videotestsrc的src pad的能力video/x-raw 来指定输入视频的宽高和格式来产生相应数据:
gst-launch-1.0 videotestsrc pattern=snow ! "video/x-raw,width=1280,height=720" ! autovideosink # pattern是videotestsrc的pad的特性,video/x-raw是pad的能力
-
-
3)Bin和Pipeline
bin 是一个容器元素,用于管理多个element。由于 bin 本身就是一个元素,因此可以以与任何其他元素相同的方式处理 bin。改变bin的状态时,bin会自动去修改所包含的element的状态,也会转发所收到的消息。如果没有bin,我们需要依次操作我们所使用的element。通过bin降低了应用的复杂度。
Pipeline继承自bin,为程序提供一个bus用于传输消息,并且对所有子element进行同步。当将pipeline的状态设置为PLAYING时,pipeline会在一个/多个新的线程中通过element处理数据。
示例:通过下面的命令播放文件时,会创建如下pipeline:
gst-launch-1.0 filesrc location=sintel_trailer-480p.ogv ! oggdemux name=demux ! queue ! vorbisdec ! autoaudiosink demux. ! queue ! theoradec ! videoconvert ! autovideosink
这个pipeline由8个element构成,每个element都实现各自的功能:
filesrc读取文件,oggdemux解析文件,分别提取audio,video数据,queue缓存数据,vorbisdec解码audio,autoaudiosink自动选择音频设备并输出;theoradec解码video,videoconvert转换video数据格式,autovideosink自动选择显示设备并输出。
-
element oggdemux说明
This element demuxes ogg files into their encoded audio and video components.
Classification: – Codec/Demuxer Rank – primary Plugin – ogg Package – GStreamer Base Plug-ins sink application/ogg: audio/ogg: video/ogg: application/kate: src_%08x ANY
3.Gstreamer通信机制
-
bus
Bus是gstreamer内部用于将消息从内部不同的streaming线程,传递到bus线程,再由bus所在线程将消息发送到应用程序。应用程序只需要向bus注册消息处理函数,即可接收到pipline中各element所发出的消息,使用bus后,应用程序就不用关心消息是从哪一个线程发出的,避免了处理多个线程同时发出消息的复杂性。
-
buffer
用于element之间数据传递,buffer中包含的是媒体数据,buffer的传输方向永远是从src pad 到sink pad的,即向下传输。
-
event
element之间或者application到element的信息传输,包含的是控制数据。event既可以向下也可以向上传输。
-
message
由element向application传输,用于传输 errors,tags,state changes, buffering state, redirects等信息。
-
queries
通常由application向pipeline发送,用于请求一些持续时间和播放点等信息。queries的应答是同步的。element也可以使用queries来向相邻element请求相应的信息
二、安装Gstreamer
# Install GStreamer on Ubuntu or Debian
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-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio
三、Gstreamer命令行使用
# 从usb video抓一次图
gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=1 ! "image/jpeg,width=1920,height=1080" ! filesink location=capture.jpg
# 从usb video定期抓图
gst-launch-1.0 --gst-debug-level=3 v4l2src device=/dev/video0 ! \
"image/jpeg,width=1920,height=1080,framerate=30/1" ! \
multifilesink location=/data/tmpfs/capture1.jpg max-files=1 post-messages=true
# 从usb video定期抓图并进行rtmp推流
gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-h264, width=1920, height=1080, framerate=20/1' ! queue ! h264parse ! flvmux ! rtmpsink location='rtmp://10.53.3.61:1935/live/camera0'
gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-raw, width=1024, height=768, framerate=30/1' ! queue ! videoconvert ! omxh264enc ! h264parse ! flvmux ! rtmpsink location='rtmp://10.53.3.61:1935/live/camera0'
# omxh264enc是第三方的h264enc插件,如果omxh264enc不支持video/x-raw,videoconvert自动将视频转换为视频接收器可以理解的格式
# 从RTSP流中定期抓图,分别进行图片保存、推流和视频保存
gst-launch-1.0 -e --gst-debug-level=3 rtspsrc location=rtsp://192.168.1.19:554/mpeg4 ! rtph264depay ! h264parse ! tee name=t \
t. ! queue ! avdec_h264 ! queue flush-on-eos=true ! videorate ! "video/x-raw,framerate=5/1" ! jpegenc ! multifilesink post-messages=true location=/data/tmpfs/camera/capture.jpg max-files=1 \ # capture
t. ! queue ! flvmux streamable=true ! rtmpsink sync=false location=rtmp://172.17.0.1/live/camera0 \ # stream
t. ! queue ! splitmuxsink max-size-time=600000000000 location=/data/tmpfs/camera/video/%06d.mp4 # video
四、Gstreamer API与程序开发
示例应用helloworld.c
#include <gst/gst.h>
#include <glib.h>
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
{
GMainLoop *loop = (GMainLoop *) data;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_EOS:
g_print ("End of stream\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error (msg, &error, &debug);
g_free (debug);
g_printerr ("Error: %s\n", error->message);
g_error_free (error);
g_main_loop_quit (loop);
break;
}
default:
break;
}
return TRUE;
}
static void on_pad_added (GstElement *element, GstPad *pad, gpointer data)
{
GstPad *sinkpad;
GstElement *decoder = (GstElement *) data;
/* We can now link this pad with the vorbis-decoder sink pad */
g_print ("Dynamic pad created, linking demuxer/decoder\n");
sinkpad = gst_element_get_static_pad (decoder, "sink");
gst_pad_link (pad, sinkpad);
gst_object_unref (sinkpad);
}
int main (int argc, char *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *source, *demuxer, *decoder, *conv, *sink;
GstBus *bus;
guint bus_watch_id;
/*init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* Check input arguments */
if (argc != 2) {
g_printerr ("Usage: %s <Ogg/Vorbis filename>\n", argv[0]);
return -1;
}
/* Create gstreamer elements */
pipeline = gst_pipeline_new ("audio-player");
source = gst_element_factory_make ("filesrc", "file-source");
demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer");
decoder = gst_element_factory_make ("vorbisdec", "vorbis-decoder");
conv = gst_element_factory_make ("audioconvert", "converter");
sink = gst_element_factory_make ("autoaudiosink", "audio-output");
if (!pipeline || !source || !demuxer || !decoder || !conv || !sink) {
g_printerr ("One element could not be created. Exiting.\n");
return -1;
}
/* Set up the pipeline */
/* we set the input filename to the source element */
g_object_set (G_OBJECT (source), "location", argv[1], NULL);
/* we add a message handler */
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
gst_object_unref (bus);
/* we add all elements into the pipeline */
/* file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output */
gst_bin_add_many (GST_BIN (pipeline),
source, demuxer, decoder, conv, sink, NULL);
/* we link the elements together */
/* file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output */
gst_element_link (source, demuxer);
gst_element_link_many (decoder, conv, sink, NULL);
g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), decoder);
/* note that the demuxer will be linked to the decoder dynamically.
The reason is that Ogg may contain various streams (for example
audio and video). The source pad(s) will be created at run time,
by the demuxer when it detects the amount and nature of streams.
Therefore we connect a callback function which will be executed
when the "pad-added" is emitted.*/
/* Set the pipeline to "playing" state*/
g_print ("Now playing: %s\n", argv[1]);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* Iterate */
g_print ("Running...\n");
g_main_loop_run (loop);
/* Out of the main loop, clean up nicely */
g_print ("Returned, stopping playback\n");
gst_element_set_state (pipeline, GST_STATE_NULL);
g_print ("Deleting pipeline\n");
gst_object_unref (GST_OBJECT (pipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);
return 0;
}
编译应用
- 确保将
PKG_CONFIG_PATH
环境变量设置为正确的位置 ($libdir/pkgconfig
)。
# 利用pkg-config获取编译此应用程序所需的编译器和链接器标志
gcc -Wall helloworld.c -o helloworld $(pkg-config --cflags --libs gstreamer-1.0)