Потоковое вещание видео с камеры с помощью GStreamer и HTML5. Часть 2: код на Си++

В прошлом посте я передал картинку с камеры на html-страницу с помощью html-тега <video> и утилиты gst-launch, которая поставляется вместе с GStreamer. Однако gst-launch не очень удобно интегировать в свои программы, поэтому я написал на Си++ свой плеер на GStreamer, который использует тот же пайплайн (то есть берет картинку из /dev/video и передает на tcp порт).
За основу был взят пример из оффициальной документации 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;
}

Комментариев нет :

Отправить комментарий