Более подробно: не получается слинковать только при указании свойства 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.
Вот так, стрингификация типов во все поля.
Ссылки: