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.