GStreamer系列之几个入门概念¶
Overview¶
GStreamer是一个多媒体框架,它可以允许你轻易地创建、编辑与播放多媒体文件,这是 通过创建带有很多特殊的多媒体元素的管道来完成的。
管道-pipeline¶
GStreamer的工作方式非常简单,你只需创建一个包含很多元素的管道,这与Linux命令行 的管道非常类似,例如,一般命令行的管道是这样的:
foo@bar:~$ ps ax | grep "apache" |wc -l
这个命令首先捕获一个进程列表然后返回名字包含 “apache” 的进程并传递给 wc 命令并
统计出行数。我们可以看出每一个使用 |
连接,并且 |
左边的命令的输出传递
给其右边的命令作为输入。
GStreamer的工作方式与此类似,GStreamer中你将很多元素串联起来,每一个元素都完成 某些特定的事。我们来演示一下:
gst-launch-1.0 filesrc location=越单纯越幸福.mp3 ! decodebin ! audioconvert ! alsasink
运行这条命令你就可以听到动听的音乐了,当然前提是你的当前目录有这个音乐文件。
gst-launch-1.0
可以用来运行 GStreamer 管道,你只需要将需要使用的元素一个一个
传递给它就可以了,每一个命令使用 !
来连接。此处你可以把 !
当作命令行里
的 |
。上面那条命令包含了几个元素,我们简单解释一下:
a. filesrc——这个元素从本地磁盘加载了一个文件,使用该元素时你设置了 location
属性指向了音乐文件,关于属性我们后边聊。
b. decodebin——我们需要从 filesrc 解码,因此我们使用了这个元素。这个元素是一个
聪明的小家伙,它会自动检测文件的类型并在后台构造一些GStreamer元素来解码。因此,
此处对于 mp3 你可以使用 mad 代替之试一下。
c. audioconvert——一个声音文件中有各种各样的信息,每种信息传递到喇叭都是不同的,
因此要使用此元素来做一下转换。
d. alsasink——这个元素做的事很简单,就是把你的音频使用ALSA传递给你的声卡。
文章写到这里,我们就可以使用管道来做各种试验了,但首先我们要知道有那些元素可以 使用啊:
foo@bar:~$ gst-inspect-1.0
这个命令列出了可用的元素,你也可以使用该命令查看某一元素的详细信息,例如 filesrc 元素:
foo@bar:~$ gst-inspect-1.0 filesrc
下面介绍一些GStreamer的相关术语,一些人可能很快就会对 pad, cap 这些术语搞晕, 就不要说 bin 和 ghost pad 了。其实这些术语都相当的简单。。。
元素element¶
其实我们已经讨论了管道,而元素就在管道上。每一个元素都有很多属性用来设置该元素。 例如, volume 元素(设置管道的音量)有一个熟悉 volume 可以设置音量或者静音。 当你创建自己的管道时就要给很多的元素设置属性。
pad¶
每一个元素都有虚拟的插头供数据流入和流出,即pad。如果你把元素看作一个对输入的 数据做一些处理的黑盒。在盒子的左右两边就是插孔了,你可以插入电缆向盒子传入信息, 这就是pad要做的事。绝大多数元素有一个输入pad(叫做sink)和一个输出pad(叫做src)。 因此,我们上面的管道看起来是这样的:
[src] ! [sink src] ! [sink src] ! [sink]
最左边的元素只有一个src pad用来提供信息(如filesrc)。接下来的几个元素接收信息并
做一些处理,因此他们有sink和src pad(例如decodebin和audiocovert),最后一个元素
只接收信息(例如alsasink)。当你使用 gst-inspect-1.0
命令查看一个元素的详细
信息时,就会列出该元素的pad信息。
注意可能与平时大家认为的概念有些不同的是,src pad是用来发送数据的端点,即数据的 输出端;而sink pad是用来接收数据的端点,即数据的输入端。
而且,一般来说,src pad只能连接到sink pad。当然,没有例外的规则是不存在的, ghost pad两端就要连接相同类型的pad,具体请参考后面的例子吧。
cap¶
我们已经了解了pad和从管道第一个元素到最后一个元素的数据流是怎么回事了,那么我们 来讨论下 cap 。每一个元素都有特定的cap,cap是指该元素可以接收什么样的信息( 例如是接收音频还是视频)。你可以把cap看成是电源插座上其可以接受什么范围电压的规则。
bin¶
很多人不理解bin,其实它很简单。bin就是一种便捷的方式,可以把很多个元素放进一个
容器中。例如你有很多个元素用来解码视频并对其使用一些效果。要使事情变得简单一些,
你可以把这些元素放进一个bin(就像一个容器)中,以后你就可以使用bin来引用这些元素了。
这样其实bin变成了一个元素,例如你的管道是 a ! b ! c ! d
,你可以把他们放进
mybin,这样当你使用mybin时其实是引用了 a ! b ! c ! d
。
ghost pad¶
当你创建了一个bin并在里面放置了很多元素时,该bin变成了你自定义的元素,该元素按 顺序调用里面的元素。要做到这样,你的bin很自然地需要它自己的pad,它自己的pad会挂接 到bin里面元素的pad上,这就是 ghost pad 了。当你创建一个bin时,你创建了ghost pad 并告诉他们要去挂接里面哪一个元素。
Example¶
话不多说,上例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, GLib
GObject.threads_init()
Gst.init(None)
class Play:
def __init__(self):
self.pipeline = Gst.Pipeline()
self.audiotestsrc = Gst.ElementFactory.make('audiotestsrc', 'audio')
# set property of element
# self.audiotestsrc.set_property('freq', 300)
print('freq:%d' %self.audiotestsrc.get_property('freq'))
self.pipeline.add(self.audiotestsrc)
self.sink = Gst.ElementFactory.make('alsasink', 'sink')
self.pipeline.add(self.sink)
self.audiotestsrc.link(self.sink)
self.pipeline.set_state(Gst.State.PLAYING)
start = Play()
loop = GLib.MainLoop()
loop.run()
|
上面这个例子很简单,使用命令行的例子来写:
gst-launch-1.0 audiotestsrc ! alsasink
gst-launch-1.0 audiotestsrc freq=300 ! alsasink
我们把上面的例子加上GUI,让它看起来是一个真正的桌面应用。
使用Glade构建的应用界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="entry1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">•</property>
<property name="invisible_char_set">True</property>
<property name="input_purpose">alpha</property>
<signal name="activate" handler="on_freq_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button1">
<property name="label" translatable="yes">Play</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image_position">bottom</property>
<signal name="clicked" handler="on_play_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button2">
<property name="label" translatable="yes">Stop</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_stop_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button3">
<property name="label" translatable="yes">Quit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">end</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_quit_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
|
程序的处理代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk
GObject.threads_init()
Gst.init(None)
class Play:
def __init__(self):
handlers = {
'on_play_clicked' : self.on_play,
'on_stop_clicked' : self.on_stop,
'on_quit_clicked' : self.on_quit,
'on_freq_changed' : self.on_freq_change,
}
self.builder = Gtk.Builder()
self.builder.add_from_file('audiotest_gui.glade')
self.builder.connect_signals(handlers)
# Gstreamer gays
self.pipeline = Gst.Pipeline()
self.audiotestsrc = Gst.ElementFactory.make('audiotestsrc', 'audio')
freq = self.audiotestsrc.get_property('freq')
self.pipeline.add(self.audiotestsrc)
self.sink = Gst.ElementFactory.make('alsasink', 'sink')
self.pipeline.add(self.sink)
self.audiotestsrc.link(self.sink)
entry = self.builder.get_object('entry1')
entry.set_text(str(freq))
win = self.builder.get_object('window1')
win.connect('delete-event', Gtk.main_quit)
win.show_all()
def on_freq_change(self, widget, *args):
print('freq changde...')
entry = self.builder.get_object('entry1')
freq = entry.get_text()
self.audiotestsrc.set_property('freq', int(freq))
def on_play(self, *args):
print('palying...')
self.pipeline.set_state(Gst.State.PLAYING)
def on_stop(self, *args):
print('stoped.')
self.pipeline.set_state(Gst.State.NULL)
#self.pipeline.set_state(Gst.State.PAUSED)
#self.pipeline.set_state(Gst.State.READY)
def on_quit(self, *args):
Gtk.main_quit()
start = Play()
Gtk.main()
|