Знакомство с GStreamer: Устройства вывода

  • Tutorial
И снова здравствуй, хабраюзер, которому интересен 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. Для чего это нужно — я даже не могу представить.

Примеры


В качестве примера мы будем использовать плеер из прошлой статьи, но с добавлением новой возможности — записью потока в файл.
example2.py
#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, и решения данной проблемы я так и не смог найти.

Заключение


Сегодня мы рассмотрели практически все имеющиеся устройства для вывода потока. В последующих статьях мы рассмотрим т. н. фильтры — элементы, которые выполняют различную работу, связанную с обработкой потоков. К фильтрам относятся различные энкодеры и декодеры, де/мультиплексоры, различные аудио/видео фильтры и прочие служебные элементы.

Литература


  1. GStreamer Application Development Manual
  2. GStreamer Core Plugins Reference Manual
  3. GStreamer Base Plugins Reference Manual
  4. GStreamer Good Plugins Reference Manual


P.S.
Уважаемые коллеги, друзья, и простые хабражители, прошу прощения за то, что опубликовал пост через полгода, а не неделю, как обещал. Озвучивать точные сроки публикации следующего поста я в этот раз не буду, т.к. боюсь в очередной раз не сдержать обещание, но постараюсь подготовить его в ближайшее время.

P.P.S.: Исходники примеров доступны на GitHub.

Предыдущая статья
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 4

    0
    А скажите, как GObject треды с питоном дружат?
      0
      Даже не знаю. Никогда не приходилось использовать треды из GObject. Зачем они, когда есть threading.* и multiprocessing.*?
      Могу сказать точно, что GObject (как и Gtk/Gdk) не очень дружит с потоками питона, и при попытке поработать с объектами из другого потока, можно получить segfault.
      0
      И про fdsink — его же можно зарулить в pipe, или в какой-нибудь twisted.*.Producer?
        0
        Да, в pipe вполне можно выводить потоки с помощью fdsink. Хоть в stdout, хоть в stderr :)

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое