Hatred's Log Place

DON'T PANIC!

Feb 11, 2020 - 4 minute read - programming

GStreamer: не получается слинковать videoconvert и appsink

Более подробно: не получается слинковать только при указании свойства caps у appsink или вставки между videoconvert и appsink фильтра capsfilter с нужным форматом. Но об этом подробнее под катом.

Итак, есть задача: используя GStreamer захватить несжатые фреймы с V4L2 устройства, преобразовать их в YUV420P и получить доступ к ним в приложении. С GStreamer раньше не работал, но в силу определённых обстоятельств, использовать нужно именно его.

Используя GStreamermm, реализация получилась такой:

#include <iostream>
#include <cassert>

#include <gstreamermm.h>
#include <glibmm/main.h>
#include <gstreamermm/appsink.h>

using namespace std;

int main(int argc, char** argv)
{
    Gst::init(argc, argv);

    auto pipeline = Gst::Pipeline::create();

    // https://gstreamer.freedesktop.org/documentation/video4linux2/v4l2src.html?gi-language=c
    auto v4l2src = Gst::ElementFactory::create_element("v4l2src");

    v4l2src->set_property<Glib::ustring>("device", "/dev/video0");
    v4l2src->set_property("io-mode", 4);
    // auto          - 0
    // rw            - 1
    // mmap          - 2
    // userptr       - 3
    // dmabuf        - 4
    // dmabuf-import - 5

    auto v4l2caps = Gst::CapsFilter::create();
    auto caps = Gst::Caps::create_simple(
                    "video/x-raw",
                    "width", 640,
                    "height", 480
                    //"framerate", Gst::Fraction(60, 1)
                    );
    v4l2caps->property_caps() = caps;

    auto csc = Gst::VideoConvert::create();

    auto appsink = Gst::AppSink::create();
    appsink->property_emit_signals() = true;
    appsink->property_sync() = false;
    appsink->signal_new_sample().connect([&appsink]() {
        auto samples = appsink->pull_sample();
        auto buffer = samples->get_buffer();
        //auto app_buffer = buffer->copy_deep();

        auto caps = samples->get_caps();

        // TBD
        std::cout << "new buffer:"
                  << " caps: " << caps->to_string()
                  << ", size=" << buffer->get_size()
                  << ", blocks=" << buffer->n_memory() << '\n';

        return Gst::FLOW_OK;
    });

    auto appcaps = Gst::Caps::create_simple(
                       "video/x-raw",
                       "format", Gst::VIDEO_FORMAT_I420
                       );
    appsink->property_caps() = appcaps;

    pipeline->add(v4l2src)->add(v4l2caps)->add(csc)->add(appsink);
    v4l2src->link(v4l2caps)->link(csc)->link(appsink);

    pipeline->set_state(Gst::STATE_PLAYING);
    auto mainloop = Glib::MainLoop::create();
    mainloop->run();
    pipeline->set_state(Gst::STATE_NULL);

    return 0;
}

Собираем его:

g++ `pkg-config gstreamermm-1.0 --cflags --libs` -o test main.cpp

Запускаем

./test

и:

terminate called after throwing an instance of 'std::runtime_error'
  what():  failed to link: gtkmm__gstvideoconvert0->gtkmm__gstappsink0
Аварийный останов (стек памяти сброшен на диск)

Анализируем. Судя по всему, какой-то косяк в согласовании выходов, хотя документация на фильтры явно говорит, что I420 они могут. Так, составим аналогичный pipeline с использованием gst-launch-1.0:

gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,width=640,height=480 ! videoconvert ! appsink caps=video/x-raw,format=I420

Запускаем и видим, что pipeline работает. WTF!?

Первое, что перебираю: динамические пады. Но нет, документация говорит, что у всех используемых фильтров пады статические.

Гуглинг, пробы, построение графов при помощи:

export GST_DEBUG="*CAPS*:7"
export GST_DEBUG_DUMP_DOT_DIR=/data

Ничего не помогает. И тут решаю, не линковать фильтры на границе videoconvert и appsink, запрашиваю напрямую выход src у videoconvert и sink у appsink и вывожу их caps, примерно так:

    auto src_pad = csc->get_static_pad("src");
    auto sink_pad = appsink->get_static_pad("sink");
    std::cout << "src caps: " << src_pad->query_caps(Glib::RefPtr<Gst::Caps>())->to_string() << "\n\n"
              << "dst caps: " << sink_pad->query_caps(Glib::RefPtr<Gst::Caps>())->to_string() << "\n\n";

Собираем и смотрим вывод:

src caps: video/x-raw, width=(int)640, height=(int)480, format=(string){ RGB16, BGR, RGB, GRAY8, GRAY16_LE, GRAY16_BE, YVU9, YV12, YUY2, YVYU, UYVY, Y42B, Y41B, YUV9, NV12_64Z32, NV24, NV61, NV16, NV21, NV12, I420, BGRA, BGRx, ARGB, xRGB, BGR15, RGB15 }, framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw, width=(int)640, height=(int)480, framerate=(fraction)[ 0/1, 2147483647/1 ], format=(string){ I420, YV12, YUY2, UYVY, AYUV, VUYA, RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, Y41B, Y42B, YVYU, Y444, v210, v216, Y210, Y410, NV12, NV21, GRAY8, GRAY16_BE, GRAY16_LE, v308, RGB16, BGR16, RGB15, BGR15, UYVP, A420, RGB8P, YUV9, YVU9, IYU1, ARGB64, AYUV64, r210, I420_10BE, I420_10LE, I422_10BE, I422_10LE, Y444_10BE, Y444_10LE, GBR, GBR_10BE, GBR_10LE, NV16, NV24, NV12_64Z32, A420_10BE, A420_10LE, A422_10BE, A422_10LE, A444_10BE, A444_10LE, NV61, P010_10BE, P010_10LE, IYU2, VYUY, GBRA, GBRA_10BE, GBRA_10LE, BGR10A2_LE, GBR_12BE, GBR_12LE, GBRA_12BE, GBRA_12LE, I420_12BE, I420_12LE, I422_12BE, I422_12LE, Y444_12BE, Y444_12LE, GRAY10_LE32, NV12_10LE32, NV16_10LE32, NV12_10LE40 }

dst caps: video/x-raw, format=(GstVideoFormat)GST_VIDEO_FORMAT_I420

Ба… а почему у src_pad свойство format имеет тип (string), а у sink_pad - (GstVideoFormat). Точнее, почему у sink_pad - я знаю, ведь я сам так назначил (и никто меня не наказал), судя по логике наименьшего удивления и строгой типизации… И тут у меня рождается мысль заменить блок:

    auto appcaps = Gst::Caps::create_simple(
                       "video/x-raw",
                       "format", Gst::VIDEO_FORMAT_I420
                       );

на

    auto appcaps = Gst::Caps::create_simple(
                       "video/x-raw",
                       "format", "I420"
                       );

Пересобираем и… ВСЁ РАБОТАЕТ!

Законченный рабочий пример:

#include <iostream>
#include <cassert>

#include <gstreamermm.h>
#include <glibmm/main.h>
#include <gstreamermm/appsink.h>

using namespace std;

int main(int argc, char** argv)
{
    Gst::init(argc, argv);

    auto pipeline = Gst::Pipeline::create();

    // https://gstreamer.freedesktop.org/documentation/video4linux2/v4l2src.html?gi-language=c
    auto v4l2src = Gst::ElementFactory::create_element("v4l2src");

    v4l2src->set_property<Glib::ustring>("device", "/dev/video0");
    v4l2src->set_property("io-mode", 4);
    // auto          - 0
    // rw            - 1
    // mmap          - 2
    // userptr       - 3
    // dmabuf        - 4
    // dmabuf-import - 5

    //auto v4l2caps = Gst::ElementFactory::create_element("capsfilter");
    auto v4l2caps = Gst::CapsFilter::create();
    auto caps = Gst::Caps::create_simple(
                    "video/x-raw",
                    //"format", "YUY2",
                    "width", 640,
                    "height", 480
                    //"framerate", Gst::Fraction(60, 1)
                    );
    v4l2caps->property_caps() = caps;

    auto csc = Gst::VideoConvert::create();

    auto appsink = Gst::AppSink::create();
    appsink->property_emit_signals() = true;
    appsink->property_sync() = false;
    appsink->signal_new_sample().connect([&appsink]() {
        auto samples = appsink->pull_sample();
        auto buffer = samples->get_buffer();
        //auto app_buffer = buffer->copy_deep();

        auto caps = samples->get_caps();

        // TBD
        std::cout << "new buffer:"
                  << " caps: " << caps->to_string()
                  << ", size=" << buffer->get_size()
                  << ", blocks=" << buffer->n_memory() << '\n';

        return Gst::FLOW_OK;
    });

    auto appcaps = Gst::Caps::create_simple(
                       "video/x-raw",
                       "format", "I420"
                       );
    appsink->property_caps() = appcaps;

    pipeline->add(v4l2src)->add(v4l2caps)->add(csc)->add(appsink);
    v4l2src->link(v4l2caps)->link(csc)->link(appsink);

    pipeline->set_state(Gst::STATE_PLAYING);
    auto mainloop = Glib::MainLoop::create();
    mainloop->run();
    pipeline->set_state(Gst::STATE_NULL);

    return 0;
}

Ну а если быть точным, то нужно смотреть на mime-тип пада, в данном случае это video/x-raw и на странице Raw Video Media Types смотреть описание, какие параметры и какого типа ему присущи, конкретно про format:

format, G_TYPE_STRING: Mandatory. The format of the video. See the Formats section for a list of valid format strings.

Вот так, стрингификация типов во все поля.

Ссылки:

Tags: programming C++ GStreamer GStreamermm

Meson Cross Compiling Thunderbird LDAP Auto-complete

comments powered by Disqus