В прошлом посте я передал картинку с камеры на 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;
}
Комментариев нет :
Отправить комментарий