И снова здравствуй, читатель, которому интересен GStreamer! Сегодня мы поговорим про устройства вывода (sink) различных медиаданных, напишем примитивный плеер для прослушивания радио и записи потока в файл, и узнаем много нового.
Устройство вывода (sink) — это элемент для вывода сигнала куда-либо, будь то звуковая карта, файл, видеокарта или сетевой интерфейс. По своей сути, устройство вывода — это полная противоположность источника данных, и в отличие от источников данных, устройства вывода имеют только один pad — sink.
Рассмотрим устройства вывода подробнее.
Поехали
1. fakesink
Данное устройство по своему смыслу аналогично fakesrc — оно ничего не делает. fakesink используется для вывода сигнала «в пустоту».
Честно говоря, я сам не могу придумать, где его можно использовать, посему особой полезности в данном устройстве я не нахожу.
Пример использования:
gst-launch-1.0 fakesrc ! fakesink
2. fdsink
Устройство fdsink используется для вывода потока в файловый дескриптор, оно, как и fdsrc, имеет только один параметр — fd, который должен содержать номер файлового дескриптора. По-умолчанию выводит поток в STDOUT. Естественно, пользы от данного элемента мало, и применять его в реальных проектах особого смысла нет.
Пример использования:
gst-launch-1.0 filesrc location=/foo/bar.mp3 ! fdsink | gst-launch-1.0 fdsrc ! decodebin ! autoaudiosink
3. alsasink, pulsesink, oss4sink/osssink, jackaudiosink, autoaudiosink
Эти элементы используются для вывода потока на звуковую карту посредством использования необходимой аудио-подсистемы. Из параметров можно отметить только device — он должен содержать в себе идентификатор звуковой карты, на которую в свою очередь будет выведен поток. Из вышеперечисленного списка модулей только autoaudiosink стоит в стороне и обладает одной особенностью — он автоматически выбирает, куда и через какую звуковую подсистему выводить поток, поэтому он не имеет параметра device.
Примеры использования:
gst-launch-1.0 filesrc location=/foo/bar.mp3 ! decodebin ! alsasink device="hw:0"
gst-launch-1.0 filesrc location=/foo/bar.mp3 ! decodebin ! pulsesink
gst-launch-1.0 filesrc location=/foo/bar.mp3 ! decodebin ! autoaudiosink
4. filesink
Как вы, наверное, уже догадались, данное устройство используется для вывода потока в файл. Его можно использовать для разных целей, например: записывать радиопоток, записывать видеопоток с web-камеры, а также аудиопоток со звуковой карты. Ко всему прочему, данное устройство просто необходимо в случае использования GStreamer как инструмента для конвертации файлов.
Подробно рассматривать свойства данного элемента мы не будем, т. к. они аналогичны свойствам элемента filesrc, с которым мы познакомились в прошлой статье. Одно отличие — у filesink имеется параметр append. Параметр append используется для дописывания потока в конец существующего файла вместо перезаписи его с начала.
Пример использования:
gst-launch-1.0 v4l2src num-buffers=1 ! jpegenc ! filesink location=/tmp/capture1.jpeg
Данный пример иллюстрирует создание фотографии первым устройством, поддерживающим v4l2, и последующее сохранение снимка в /tmp/capture1.jpeg.
5. multifilesink
Элемент multifilesink — полная противоположность элементу multifilesrc, с которым мы познакомились в прошлой статье, и используется он для вывода потока в разные файлы. Параметры данного элемента аналогичны параметрам multifilesrc, поэтому на них мы останавливаться не будем.
Пример использования:
gst-launch-1.0 v4l2src num-buffers=10 ! jpegenc ! multifilesink location=/tmp/capture%d.jpeg
Данный пример иллюстрирует создание 10 фотографий и сохранение их в файлы capture0.jpeg-capture9.jpeg.
6. giosink
И этот элемент является полной противоположностью элементу giosrc — он используется для вывода потока в файл через GIO. Как и giosrc, giosink имеет параметр location, содержащий путь к файлу, в который необходимо записать поток.
Пример использования:
gst-launch-1.0 location ! giosink location=file:///foo/bar.raw
7. ximagesink и xvimagesink
Данные элементы используются для вывода видеосигнала посредством X-сервера. Эти элементы могут использоваться как для просмотра видео «в консоли», так и для реализации вывода видео в приложениях. Разница между элементами небольшая, но есть, и она заключается в двух моментах — ximagesink использует только X-сервер для вывода, а xvimagesink — libxv. Так же xvimagesink имеет чуть больше параметров. Рассмотрим их:
display
Имя X-дисплея, например :0, :1, :2…
pixel-aspect-ratio
Данный параметр указывает соотношение сторон, например 2/1. По умолчанию имеет значение 1/1.
force-aspect-ratio
В некоторых случаях явное указывание pixel-aspect-ratio может не сработать (в случае если «переговоры» между элементами привели к тому, что нужно оставить оригинальный pixel-apect-ratiio), и данное свойство исправляет эту «проблему».
Далее перечисляются свойства, имеющиеся только у xvimagesink.
brightness, contrast, hue, saturation
Переведя на русский язык названия этих свойств («яркость-контрастность-оттенок-насыщенность), можно понять их назначение. Значения могут располагаться в диапазоне от -1000 до 1000.
device
Порядковый номер видеокарты, с помощью которой необходимо выводить видео.
double-buffer
Данное свойство включает и выключает использование двойной буферизации.
colorkey, autopaint-colorkey
Данные свойства используются для управления цветом оверлея, на котором рисуется видео. Colorkey должно в себе содержать gint с кодом цвета, а autopaint-colorkey включает «заливку» оверлея этим цветом.
Примечание:
В документации отсутствуют пояснения по поводу того, что из себя представляет цвет, но, скорее всего, цвет указывается в RGB формате, по формуле ((RR & 0xff) << 16) | ((GG & 0xff) << 8 ) | (BB & 0xff).
draw-borders
Данное свойство включает или отключает отрисовку черной обводки в местах, где образовалась «пустота» при применении force-aspect-ratio.
Примеры использования:
gst-launch-1.0 videotestsrc ! ximagesink
gst-launch-1.0 videotestsrc ! xvimagesink
8. aasink и cacasink
Эти элементы уже, наверно, не актуальны, и могут использоваться либо «олдфагами», либо теми, кто хочет показать «что могут линуксы», хотя, возможно, я и ошибаюсь. Оба этих элемента позволяют выводить видео посредством библиотек libaa и libcaca, то есть выводить видео в виде ASCII-арта. Различие между ними только одно: libaa выводит черно-белые символы, а libcaca — цветные.
Останавливаться на параметрах данных элементов мы не будем, т. к. практической пользы от них (ИМХО) нет.
Примеры использования:
gst-launch-1.0 videotestsrc ! aasink
gst-launch-1.0 videotestsrc ! aacasink
9. gdkpixbufsink
Данный элемент выводит видеопоток в объект GdkPixbuf, который доступен через read-only свойство last-pixbuf. Для чего это нужно — я даже не могу представить.
Примеры
В качестве примера мы будем использовать плеер из прошлой статьи, но с добавлением новой возможности — записью потока в файл.
#env python2
# coding=utf-8
import gi
gi.require_version("Gst", "1.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Gst
from gi.repository import Gtk
from gi.repository import GObject
import os
import signal
import argparse
Gst.init("")
signal.signal(signal.SIGINT, signal.SIG_DFL)
GObject.threads_init()
def parse_args():
parser = argparse.ArgumentParser(prog='example1.py')
parser.add_argument('--volume', help='Указать громкость (0-100) (default: 100)', type=int, default=100)
parser.add_argument('--output', help='Путь к файлу в который нужно сохранить поток (default: /tmp/out.ogg)',
type=str, default='/tmp/out.ogg')
parser.add_argument('location')
args = parser.parse_args()
return args
class RecorderBin(Gst.Bin):
def __init__(self, name=None):
super(RecorderBin, self).__init__(name=name)
self.vorbisenc = Gst.ElementFactory.make("vorbisenc", "vorbisenc")
self.oggmux = Gst.ElementFactory.make("oggmux", "oggmux")
self.filesink = Gst.ElementFactory.make("filesink", "filesink")
self.add(self.vorbisenc)
self.add(self.oggmux)
self.add(self.filesink)
self.vorbisenc.link(self.oggmux)
self.oggmux.link(self.filesink)
self.sink_pad = Gst.GhostPad.new("sink", self.vorbisenc.get_static_pad("sink"))
self.add_pad(self.sink_pad)
def set_location(self, location):
self.filesink.set_property("location", location)
class Player():
def __init__(self, args):
self.pipeline = self.create_pipeline(args)
self.args = args
## получаем шину по которой рассылаются сообщения
## и вешаем на нее обработчик
message_bus = self.pipeline.get_bus()
message_bus.add_signal_watch()
message_bus.connect('message', self.message_handler)
## устанавливаем громкость
self.pipeline.get_by_name('volume').set_property('volume', args.volume / 100.)
def create_source(self, location):
"""create_source(str) -> Gst.Element"""
if not location.startswith('http') and not os.path.exists(location):
raise IOError("File %s doesn't exists" % location)
if location.startswith('http'):
source = Gst.ElementFactory.make('souphttpsrc', 'source')
else:
source = Gst.ElementFactory.make('filesrc', 'source')
source.set_property('location', location)
return source
def create_pipeline(self, args):
"""create_pipeline() -> Gst.Pipeline"""
pipeline = Gst.Pipeline()
## Создаем нужные элементы для плеера
source = self.create_source(args.location)
decodebin = Gst.ElementFactory.make('decodebin', 'decodebin')
audioconvert = Gst.ElementFactory.make('audioconvert', 'audioconvert')
volume = Gst.ElementFactory.make('volume', 'volume')
audiosink = Gst.ElementFactory.make('autoaudiosink', 'autoaudiosink')
## Элемент tee используется для мультиплексирования потока
tee = Gst.ElementFactory.make('tee', 'tee')
## decodebin имеет динамические pad'ы, которые так же динамически
## необходимо линковать
def on_pad_added(decodebin, pad):
pad.link(audioconvert.get_static_pad('sink'))
decodebin.connect('pad-added', on_pad_added)
## добавляем все созданные элементы в pipeline
elements = [source, decodebin, audioconvert, volume, audiosink, tee]
[pipeline.add(k) for k in elements]
## линкуем элементы между собой по схеме:
## +-> volume -> autoaudiosink
## *src* -> (decodebin + audioconvert) -> tee -> |
## [ +-> vorbisenc -> oggmux -> filesink ]
source.link(decodebin)
audioconvert.link_pads('src', tee, 'sink')
tee.link_pads('src_0', volume, 'sink')
volume.link(audiosink)
return pipeline
def play(self):
self.pipeline.set_state(Gst.State.PLAYING)
recorder = RecorderBin('recorder')
self.pipeline.add(recorder)
self.pipeline.get_by_name('tee').link_pads('src_1', recorder, 'sink')
recorder.set_location(self.args.output)
def message_handler(self, bus, message):
"""Обработчик сообщений"""
struct = message.get_structure()
if message.type == Gst.MessageType.EOS:
print('Воспроизведение окончено.')
Gtk.main_quit()
elif message.type == Gst.MessageType.TAG and message.parse_tag() and struct.has_field('taglist'):
print('GStreamer обнаружил в потоке мета-теги')
taglist = struct.get_value('taglist')
for x in range(taglist.n_tags()):
name = taglist.nth_tag_name(x)
print(' %s: %s' % (name, taglist.get_string(name)[1]))
else:
pass
if __name__ == "__main__":
args = parse_args()
player = Player(args)
player.play()
Gtk.main()
Примечание:
Данный пример (как и пример из прошлой статьи), не работает в Ubuntu 13.10, падая с segfault (см. lp:1198375).
Рассмотрим, что тут происходит. Для удобства и логического разделения создаем контейнер RecorderBin, в который помещаем три элемента — vorbisenc, oggmux и filesink. Элементы vorbisenc и oggmux необходимы для кодирования RAW-потока в формат vorbis и для заворачивания его в контейнер ogg соответственно. Подробно на контейнерах (bin) мы останавливаться не будем, напомню только то, что контейнеры являются законченными элементами, которые выполняют какое-либо действие в pipeline.
В RecorderBin все три элемента линкуются между собой последовательно, по схеме:
vorbisenc → oggmux → filesink
Далее мы создаем элемент tee, необходимый для мультиплексирования потока, т. к. большинство элементов, как вы помните, зачастую имеют только один вход и один выход, а элемент tee решает проблему, возникающую, когда нужно «разделить» сигнал и отправить его в две разных точки (звуковая карта и файл в нашем случае).
После этого мы линкуем выход src_0 элемента tee с входом sink элемента volume, а в методе play, после установки статуса PLAYING, добавляем в pipeline наш RecorderBin и линкуем выход src_1 элемента tee с sink RecorderBin-а.
По логике, слинковать все можно было бы и в create_pipeline, но в GStreamer по какой-то причине блокируется весь pipeline при добавлении еще одного sink-элемента до установки статуса PLAYING, и решения данной проблемы я так и не смог найти.
Заключение
Сегодня мы рассмотрели практически все имеющиеся устройства для вывода потока. В последующих статьях мы рассмотрим т. н. фильтры — элементы, которые выполняют различную работу, связанную с обработкой потоков. К фильтрам относятся различные энкодеры и декодеры, де/мультиплексоры, различные аудио/видео фильтры и прочие служебные элементы.
Литература
- GStreamer Application Development Manual
- GStreamer Core Plugins Reference Manual
- GStreamer Base Plugins Reference Manual
- GStreamer Good Plugins Reference Manual
P.P.S.: Исходники примеров доступны на GitHub.
Автор: POPSuL