- PVSM.RU - https://www.pvsm.ru -

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

И снова здравствуй, читатель, которому интересен 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

Данное свойство включает и выключает использование двойной буферизации [1].

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 [2], который доступен через 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 [3]).

Рассмотрим, что тут происходит. Для удобства и логического разделения создаем контейнер RecorderBin, в который помещаем три элемента — vorbisenc, oggmux и filesink. Элементы vorbisenc и oggmux необходимы для кодирования RAW-потока в формат vorbis [4] и для заворачивания его в контейнер ogg [5] соответственно. Подробно на контейнерах (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 [6]
  2. GStreamer Core Plugins Reference Manual [7]
  3. GStreamer Base Plugins Reference Manual [8]
  4. GStreamer Good Plugins Reference Manual [9]
P.S.

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

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

Автор: POPSuL

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka/49488

Ссылки в тексте:

[1] двойной буферизации: http://ru.wikipedia.org/wiki/Двойная_буферизация

[2] GdkPixbuf: https://developer.gnome.org/gdk-pixbuf/unstable/gdk-pixbuf-The-GdkPixbuf-Structure.html

[3] lp:1198375: https://bugs.launchpad.net/ubuntu/+source/rhythmbox/+bug/1198375

[4] vorbis: http://ru.wikipedia.org/wiki/Vorbis

[5] ogg: http://ru.wikipedia.org/wiki/Ogg_(контейнер)

[6] GStreamer Application Development Manual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/index.html

[7] GStreamer Core Plugins Reference Manual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-plugins/html/

[8] GStreamer Base Plugins Reference Manual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/

[9] GStreamer Good Plugins Reference Manual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/

[10] GitHub: https://github.com/POPSuL/gst-examples

[11] Источник: http://habrahabr.ru/post/204014/