Потоковое вещание видео с камеры с помощью 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#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

1
2
3
4
5
6
7
8
9
10
11
#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;
}

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

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