В прошлом посте я передал картинку с камеры на html-страницу с помощью html-тега <video> и утилиты gst-launch, которая поставляется вместе с GStreamer. Однако gst-launch не очень удобно интегировать в свои программы, поэтому я написал на Си++ свой плеер на GStreamer, который использует тот же пайплайн (то есть берет картинку из /dev/video и передает на tcp порт).
За основу был взят пример из оффициальной документации GStreamer: Your first application.
Что я сделал:
Кода получилось немного, но достаточно, чтоб здесь это выглядело не очень удобно. Поэтому весь код я залил на github.
Здесь приведу только код класса Player:
За основу был взят пример из оффициальной документации GStreamer: Your first application.
Что я сделал:
- Изменил пайплайн на тот, который был нужен мне (v4l2src ! theoraenc ! oggmux ! tcpserversink).
- На основе кода примера создал отдельный класс Player, в котором инкапсулировал все данные, связаные с GStreamer.
- Обернул сырые указатели std::shared_ptr с кастомными функциями удаления, чтоб не вызывать вручную функции освобождения объектов GStreamer типа gst_object_unref.
- Создал класс СonfigManager, который используется в классе Player, чтобы читать настройки из файла (разрешение, номер порта и т.д.). Настройки хранятся в XML файле, который парсится с помощью TinyXML2.
- Ну и написал makefile (на самом деле, почти полностью скопировал отсюда).
Кода получилось немного, но достаточно, чтоб здесь это выглядело не очень удобно. Поэтому весь код я залил на github.
Здесь приведу только код класса Player:
Файл player.h
#ifndef PLAYER_H_ #define PLAYER_H_ #include <gst/gst.h> #include <glib.h> #include <memory> #include <string> #include "config-manager.h" class Player { public: Player(); ~Player(); void startPlayer(); void loadConfig(const std::string & filename = ""); private: static gboolean busCallHandlerWrapper (GstBus *bus, GstMessage *msg, gpointer data); gboolean busCallHandler(GstBus *bus, GstMessage *msg, gpointer data); std::shared_ptr<GMainLoop> loopSp; std::shared_ptr<GstElement> pipelineSp; std::shared_ptr<GstElement> sourceSp; std::shared_ptr<GstElement> encoderSp; std::shared_ptr<GstElement> muxerSp; std::shared_ptr<GstElement> sinkSp; std::shared_ptr<GstElement> filterSp; guint busWatchId; ConfigManager cm; };
Файл player.cpp
#include "player.h" #include "debug/debug.h" #include <exception> #include <memory> void Player::startPlayer() { /* Set up the pipeline */ g_object_set (G_OBJECT (sinkSp.get()), "host", cm.getHost().c_str(), NULL); g_object_set (G_OBJECT (sinkSp.get()), "port", cm.getPort(), NULL); g_object_set (G_OBJECT (sourceSp.get()), "device", cm.getVideodev().c_str(), NULL); std::shared_ptr<GstCaps> capsSp( gst_caps_new_simple ( "video/x-raw-yuv", "width", G_TYPE_INT, cm.getWidth(), "height", G_TYPE_INT, cm.getHeight(), NULL), gst_caps_unref); g_object_set(G_OBJECT(filterSp.get()), "caps", capsSp.get(), NULL); /* we add a message handler */ std::shared_ptr<GstBus> busSp( gst_pipeline_get_bus(GST_PIPELINE (pipelineSp.get())), gst_object_unref); busWatchId = gst_bus_add_watch ( busSp.get(), busCallHandlerWrapper, static_cast<gpointer>(this)); /* we add all elements into the pipeline */ gst_bin_add_many(GST_BIN (pipelineSp.get()), sourceSp.get(), filterSp.get(), encoderSp.get(), muxerSp.get(), sinkSp.get(), NULL); gst_element_link_many( sourceSp.get(), filterSp.get(), encoderSp.get(), muxerSp.get(), sinkSp.get(), NULL); gst_element_set_state (pipelineSp.get(), GST_STATE_PLAYING); DEBUG_PRINT(DL_INFO, "Starting playback\n"); g_main_loop_run (loopSp.get()); } void Player::loadConfig(const std::string & filename) { if(!filename.empty()) { cm.loadConfig(filename); } } Player::Player() { gst_init (NULL, NULL); loopSp.reset(g_main_loop_new(NULL, FALSE), g_main_loop_unref); pipelineSp.reset( gst_pipeline_new("video-player"), gst_object_unref); sourceSp.reset( gst_element_factory_make("v4l2src", "camera-source"), gst_object_unref); filterSp.reset( gst_element_factory_make ("capsfilter", "filter"), gst_object_unref); encoderSp.reset( gst_element_factory_make ("theoraenc", "encoder"), gst_object_unref); muxerSp.reset( gst_element_factory_make ("oggmux", "muxer"), gst_object_unref); sinkSp.reset( gst_element_factory_make ("tcpserversink", "tcp-sink"), gst_object_unref); if (!loopSp || !pipelineSp || !sourceSp || !filterSp || !encoderSp || !muxerSp || !sinkSp) { DEBUG_PRINT(DL_ERROR, "One of pipeline elements could not \ be created. Player not started.\n"); throw; } } Player::~Player() { gst_element_set_state (pipelineSp.get(), GST_STATE_NULL); g_source_remove (busWatchId); } gboolean Player::busCallHandlerWrapper(GstBus* bus, GstMessage* msg, gpointer data) { Player * player = static_cast<Player *>(data); return player->busCallHandler(bus, msg, player->loopSp.get()); } gboolean Player::busCallHandler(GstBus* bus, GstMessage* msg, gpointer data) { GMainLoop *loop = static_cast<GMainLoop *>(data); switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_EOS: DEBUG_PRINT(DL_INFO, "End of stream\n"); g_main_loop_quit (loopSp.get()); break; case GST_MESSAGE_ERROR: gchar *debug; GError *error; gst_message_parse_error (msg, &error, &debug); g_free (debug); DEBUG_PRINT(DL_ERROR, "Error: %s\n", error->message); g_error_free (error); g_main_loop_quit (loopSp.get()); break; default: break; } return TRUE; }
Файл main.cpp
#include "player.h" #include "debug/debug.h" #include <pthread.h> #include <exception> int main(void) { Player player; player.loadConfig("config.xml"); player.startPlayer(); return 0; }
Комментариев нет :
Отправить комментарий