Streaming V4L2 feed to Gtk::DrawingArea

I want to embed stream that comes from a V4L2 device (e.g. a webcam) into Gtk::DrawingArea, and allow user to toggle the stream with Space key. When the streaming is disabled, I want to display black background. (Default streaming state is “off.”)

Here is a sample code.

#include <gtkmm.h>
#include <gstreamermm.h>
#include <string>
#include <unordered_map>

extern "C"
{
	#include <gdk/gdkx.h>
	#include <gdk/gdkkeysyms.h>
}

#define V4L2_DEVICE	"/dev/video0"

class PreviewWindow : public Gtk::Window
{
	public:
		PreviewWindow(Gst::Pipeline& pipeline)
			: screen(pipeline)
		{
			set_title("Preview");
			set_default_size(640, 480);
			add_events(Gdk::KEY_PRESS_MASK);

			add(screen);
		}

		virtual ~PreviewWindow() = default;

		virtual bool on_key_press_event(GdkEventKey *event) override
		{
			if (event->keyval == GDK_KEY_KP_Space)
			{
				static bool on = false;
				(on = !on) ? screen.start() : screen.stop();
			}

			return false;
		}

	protected:
		class Screen : public Gtk::DrawingArea
		{
			public:
				Screen(Gst::Pipeline& pipeline)
					: pipeline(pipeline)
				{
					Glib::RefPtr<Gst::Bus> bus = pipeline.get_bus();
					bus->enable_sync_message_emission();
					bus->signal_sync_message().connect(sigc::mem_fun(*this, &PreviewWindow::Screen::on_bus_msg_sync));
				}

				virtual ~Screen() = default;

				virtual void start()
				{
					pipeline.set_state(Gst::State::STATE_PLAYING);
				}

				virtual void stop()
				{
					pipeline.set_state(Gst::State::STATE_READY);
				}

			protected:
				inline virtual Glib::RefPtr<Gst::VideoOverlay> find_overlay(Glib::RefPtr<Gst::Element> element)
				{
					  auto overlay = Glib::RefPtr<Gst::VideoOverlay>::cast_dynamic(element);

					  if (overlay)
						return overlay;

					  auto bin = Glib::RefPtr<Gst::Bin>::cast_dynamic(element);

					  if (!bin)
						return overlay;

					  for (auto e : bin->get_children())
					  {
						overlay = find_overlay(e);
						if (overlay)
						  break;
					  }

					  return overlay;
				}

				virtual void on_bus_msg_sync(const Glib::RefPtr<Gst::Message>& message)
				{
					  if (!gst_is_video_overlay_prepare_window_handle_message (message->gobj()))
						return;

					  if (window_handler != 0)
					  {
						Glib::RefPtr<Gst::VideoOverlay> overlay = find_overlay(Glib::RefPtr<Gst::Element>::cast_dynamic(message->get_source()));

						if(overlay)
						  overlay->set_window_handle(window_handler);
					  }
				}

				virtual void on_realize() override
				{
					window_handler = GDK_WINDOW_XID(get_window()->gobj());
				}

				virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override
				{
					std::unordered_map<std::string, Gst::State> states;

					pipeline.get_state(states["current"], states["pending"], Gst::CLOCK_TIME_NONE);

					if (states["current"] < Gst::State::STATE_PAUSED)
					{
						const Gtk::Allocation allocation = get_allocation();

						cr->set_source_rgb(0.0, 0.0, 0.0);
						cr->rectangle(0.0, 0.0, allocation.get_width(), allocation.get_height());
						cr->fill();

						return true;
					}

					return false;
				}

				guint window_handler;

			private:
				Gst::Pipeline& pipeline;
		};

	private:
		Screen screen;
};

class PreviewPipeline : public Gst::Pipeline
{
	public:
		PreviewPipeline()
		{
			elements[ElementType::SOURCE] = Gst::ElementFactory::create_element("v4l2src");
			elements[ElementType::CONVERTER] = Gst::ElementFactory::create_element("videoconvert");
			elements[ElementType::SINK] = Gst::ElementFactory::create_element("autovideosink");

			elements[ElementType::SOURCE]->set_property<Glib::ustring>("device", V4L2_DEVICE);

			for (const auto& element : elements)
				add(element.second);

			elements[ElementType::SOURCE]->link(elements[ElementType::CONVERTER])->link(elements[ElementType::SINK]);
		}

		virtual ~PreviewPipeline()
		{
			set_state(Gst::State::STATE_NULL);
		}

	protected:
		enum class ElementType {
								SOURCE,
								CONVERTER,
								SINK
							};

	private:
		std::unordered_map<ElementType, Glib::RefPtr<Gst::Element>> elements;
};

int main(int argc, char *argv[])
{
	Gst::init(argc, argv);
	Gtk::Main app(argc, argv);

	PreviewPipeline pipeline;
	PreviewWindow preview(pipeline);

	Gtk::Main::run(preview);
}

This program just displays a blank window, and no streaming to the preview screen is performed.

I need someone to explain what am I doing wrong and what do I need to change to get the desired behavior. Also note that I am using GTKmm and GStreamermm, so please don’t give me help that involves (native) GTK or GStreamer.

My first question would be: why not use a GstGtkGLSink element (which you can create with "gtkglsink")? Using autovideosink is nice to quickly get something working from the start, but you don’t really have any control on which element you’re exactly going to get.

When using GstGtkGLSink, you can use the "widget" property (inherited from the GstGtkBaseSink) to get a Gtk::Widget and plug it in the widget hierarchy, rather than having to mess around with a Gtk::DrawingArea.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.