File: gst.cpp

package info (click to toggle)
cppgir 2.0%2Bgit20240928.c8bb1c6%2Breally2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,228 kB
  • sloc: cpp: 14,307; ansic: 339; makefile: 11; sh: 9
file content (381 lines) | stat: -rw-r--r-- 12,352 bytes parent folder | download | duplicates (2)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <sstream>
#include <vector>

#include "assert.h"

//#define GI_INLINE 1
#include <gst/gst.hpp>

namespace GLib = gi::repository::GLib;
namespace Gst = gi::repository::Gst;
namespace GObject_ = gi::repository::GObject;

void
say(const std::string &msg)
{
  std::cout << msg << std::endl;
}

void
die(const std::string &why)
{
  std::cerr << why << std::endl;
  exit(2);
}

class Player
{
  typedef Player self;

  GLib::MainLoop loop_;
  std::string url_;

  Gst::Element playbin_;
  bool live_ = false;
  Gst::Element vfilter_;
  Gst::Element afilter_;
  std::map<std::string, Gst::Element> filter_;
  GLib::SourceScopedConnection monitor_;
  GObject_::SignalConnection busconn_;

public:
  Player(GLib::MainLoop loop, const std::string &url) : loop_(loop), url_(url)
  {
    playbin_ = Gst::ElementFactory::make("playbin");
    assert(playbin_);
    // get a property
    // this one is known at introspection time
    // (so type is known and suitable checked)
    auto name = playbin_.property_name().get();
    std::cout << "created playbin " << name << std::endl;
  }

  void start()
  {
    // set some property
    playbin_.set_property("uri", url_);
    // ... or several ones
    playbin_.set_properties("volume", 0.5, "mute", false);
    // ... non-primitive also possible
    vfilter_ = Gst::ElementFactory::make("identity", "vfilter");
    afilter_ = Gst::ElementFactory::make("identity", "afilter");
    playbin_.set_properties("video-filter", vfilter_, "audio-filter", afilter_);

    // connect some; find out what the source element is
    // signal not known to introspection, so have to provide signature here
    playbin_.connect<void(Gst::Element, Gst::Element)>(
        "source-setup", gi::mem_fun(&self::on_source_setup, this));
    // for a known signal, it is a bit easier to connect
    auto bus = playbin_.get_bus();
    bus.add_signal_watch();
    // signal connect; using introspected (hence checked) signal definition
    // could do without the slot step here and directly pass the lambda,
    // but wrapping it this way allows combining this with the returned
    // (plain) id into a scoped connection guard
    auto slot = bus.signal_message().slot(gi::mem_fun(&self::on_message, this));
    busconn_ =
        gi::make_connection(bus.signal_message().connect(slot), slot, bus);
    // again, if the guard is not desired; the following simply suffices
    if (false)
      bus.signal_message().connect(gi::mem_fun(&self::on_message, this));
    // if the signal signature is somehow not supported
    // then the following is a fallback manual method
    // which still allows to use lambda (and such)
    // and also provides some ownership management
    if (false) {
      auto h = [](GstBus *, GstMessage *) {
        // dummy no-op
      };
      bus.connect_unchecked<void(GstBus *, GstMessage *)>("message", h);
    }
    // likewise, if a callback is to be used whose arguments are not supported
    // or in a function that is not supported, then the following is a fallback
    // which still allows to use lambda (and such)
    // and also provides some ownership management
    if (false) {
      auto h = []() { return false; };
      auto cb = new gi::callback_wrapper<gboolean(), false>(h);
      // now the above pointer is managed as user data
      // and will be suitable destroyed when needed
      g_idle_add_full(0, &cb->wrapper, cb, &cb->destroy);
      // in case of a typical single-use async callback (with no GDestroyNotify)
      // use true as AUTODESTROY template parameter
      // (then it will auto-clean up after invoking callback)
    }

    say("Setting pipeline to PAUSED ...");
    auto ret = playbin_.set_state(Gst::State::PAUSED_);
    if (ret == Gst::StateChangeReturn::FAILURE_) {
      die("Pipeline does not want to pause");
    } else if (ret == Gst::StateChangeReturn::NO_PREROLL_) {
      say("Pipeline is live and does not need PREROLL ...");
      live_ = true;
    } else if (ret == Gst::StateChangeReturn::ASYNC_) {
      say("Pipeline is PREROLLING ...");
    }

    // inspect after a while
    GLib::timeout_add_seconds(2, [this]() {
      inspect();
      return GLib::SOURCE_REMOVE_;
    });

    // some regular progress reporting ...
    GLib::SourceFunc func = [this]() {
      progress();
      return GLib::SOURCE_CONTINUE_;
    };
    // the introspected function returns a plain id,
    // which can be used in the usual way to disconnect
    // e.g. at destructor time of owning object
    // alternatively, a helper scoped object can take care of that
    monitor_ = gi::make_connection(GLib::timeout_add_seconds(1, func), func);
    // other such make_connection variations exist;
    // e.g. for a signal connection, a probe callback
    // (and can easily be custom added)

    // add a pad probe; use a casual lambda
    // but not too casual, mind (dangling) references/pointers though
    filter_["video"] = vfilter_;
    filter_["audio"] = afilter_;
    for (auto &&p : filter_) {
      if (p.second) {
        Gst::Pad pad = p.second.get_static_pad("sink");
        auto name = p.first;
        auto handler = [name](Gst::Pad p, Gst::PadProbeInfo_Ref info) {
          auto s = p.get_path_string();
          s += "received " + name + " buffer";
          auto buffer = info.get_buffer();
          if (buffer) {
            s += " of size ";
            s += std::to_string(buffer.get_size());
          }
          return Gst::PadProbeReturn::REMOVE_;
        };
        pad.add_probe(Gst::PadProbeType::BUFFER_, handler);
      }
    }

    // shamelessly demo some helpers that aid in caps/value handling
    auto caps = Gst::Caps::new_empty_simple("video/x-raw");
    caps.set_value("width", GObject_::Value(Gst::IntRange(240, 320)));
    caps.set_value("pixel-aspect-ratio", GObject_::Value(Gst::Fraction(4, 3)));
    caps.set_value(
        "framerate", GObject_::Value(Gst::FractionRange({25, 1}, 30)));
    // retrieving pretty much the same way
    auto s = caps.get_structure(0);
    auto par = s.get_value("pixel-aspect-ratio").get_value<Gst::Fraction>();
    // helpers also stream to string properly
    std::ostringstream oss;
    oss << par;
    oss << (Gst::FlagSet(1, 1) == Gst::FlagSet(2, 1));
    // silly test code to exercise some operators
    // Rank override allows for succinct numeric conversion
    if (+Gst::Rank::PRIMARY_ + 0) {
      // flags support various typical operations
      (void)(Gst::PadProbeType::BUFFER_ | Gst::PadProbeType::BUFFER_LIST_);
    }
  }

  void stop()
  {
    if (playbin_)
      playbin_.set_state(Gst::State::NULL_);
    loop_.quit();
  }

  void on_message(Gst::Bus /*bus*/, Gst::Message_Ref msg)
  {
    auto &&src = msg.src_();
    switch (msg.type_()) {
      case Gst::MessageType::EOS_:
        say("Got EOS from " + src.get_path_string());
        stop();
        break;
      case Gst::MessageType::ERROR_: {
        GLib::Error err;
        gi::cstring debug;
        msg.parse_error(&err, &debug);
        say("Got error from " + src.get_path_string());
        if (debug.size())
          say("debug info:\n" + debug);
        break;
      }
      case Gst::MessageType::STATE_CHANGED_:
        /* only handle top-level case */
        if (src != playbin_)
          break;
        Gst::State old, new_, pending;
        msg.parse_state_changed(&old, &new_, &pending);
        if (new_ == Gst::State::PAUSED_ && old == Gst::State::READY_)
          playbin_.set_state(Gst::State::PLAYING_);
        break;
      default:
        // never mind
        break;
    }
  }

  void on_source_setup(Gst::Element /*pb*/, Gst::Element src)
  {
    say("source is " + src.get_path_string());
  }

  void inspect()
  {
    std::ostringstream oss;
    // this should work
    auto bin = gi::object_cast<Gst::Bin>(playbin_);
    assert(bin);
    // a dynamically loaded element may implement a number of interfaces
    // which is not known at compile-time
    // the cast above can also cast to interface, which can be obtained as
    // follows if known at introspection/compile time
    auto cp = bin.interface_(gi::interface_tag<Gst::ChildProxy>());
    oss << "player bin has " << cp.get_children_count() << " children"
        << std::endl;
    // get some properties (dynamically, i.e. not known at introspection
    // time) type will have to match (i.e. transformable) at runtime
    auto n_v = playbin_.get_property<int>("n-video");
    auto n_a = playbin_.get_property<int>("n-audio");
    // minimal stuff
    oss << "sample streams:  video=" << n_v << ", audio=" << n_a << std::endl;
    // show some tag info
    for (auto &&p :
        std::map<std::string, int>{{"video", n_v}, {"audio", n_a}}) {
      for (int i = 0; i < p.second; ++i) {
        // the argument's type should match the signal definition
        // (cast if needed to make it so)
        auto action = std::string("get-");
        action += p.first + "-tags";
        auto taglist = playbin_.emit<Gst::TagList>(action, i);
        if (!taglist)
          continue;
        auto ntags = taglist.n_tags();
        oss << p.first << " stream " << i << std::endl;
        for (int j = 0; j < ntags; ++j) {
          auto tname = taglist.nth_tag_name(j);
          auto value = taglist.get_value_index(tname, 0);
          try {
            auto sval = value.transform_value<std::string>();
            oss << "  " << tname << ": " << sval << std::endl;
          } catch (...) {
            // could be object or otherwise, never mind
          }
        }
      }
    }

    // should also have caps here by now
    // there are other ways to obtain this, but let's go this way here
    for (auto &&p : filter_) {
      if (p.second) {
        Gst::Pad pad = p.second.get_static_pad("sink");
        auto caps = pad.get_current_caps();
        oss << p.first << " caps: " << caps.to_string() << std::endl;
      }
    }

    say(oss.str());

    say("Playbin elements:");
    // Gst::Iterator could be used with native interface
    // but a helper wrapper has been provided that supports ease-of-use as
    // in ...
    for (auto &&el : Gst::IteratorAdapter<Gst::Element>(bin.iterate_recurse()))
      say(el.get_path_string());
    say("");
  }

  static std::string time_to_str(Gst::ClockTime time)
  {
    // could be done otherwise
    // but we have native C access at hand, so let's use that
    auto s = g_strdup_printf("%" GST_TIME_FORMAT, GST_TIME_ARGS(time));
    std::string ret(s);
    g_free(s);
    return ret;
  }

  void progress()
  {
    bool ok = true;
    gint64 duration = -1, position = -1;
    ok &= playbin_.query_duration(Gst::Format::TIME_, &duration);
    ok &= playbin_.query_position(Gst::Format::TIME_, &position);
    std::ostringstream oss;
    if (ok) {
      oss << "Duration: " << time_to_str(duration)
          << ", Position: " << time_to_str(position);
    } else {
      oss << "No progress info available";
    }
    say(oss.str());
  }
};

// not used in the above, but serves as a subclass example
class ChattyBin : public Gst::impl::BinImpl
{
public:
#if 0
  // this part is only needed if there is some conflict
  // (among members of class and/or interfaces)
  // otherwise it should be auto-detected
  struct DefinitionData
  {
    GI_DEFINES_MEMBER(BinClassDef, add_element, true)
  };
#endif

  ChattyBin() : Gst::impl::BinImpl(this) {}

  bool add_element_(Gst::Element element) noexcept override
  {
    say("adding element " + element.name_() + '\n');
    return Gst::impl::BinImpl::add_element_(element);
  }
};

int
main(int argc, char **argv)
{
  if (argc < 2)
    die("missing argument");

  // C signature fits C main best anyway
  gst_init(&argc, &argv);

  std::string url = argv[1];
  // make it URL if not so
  if (!Gst::Uri::is_valid(url)) {
    try {
      url = gi::expect(Gst::filename_to_uri(url));
    } catch (const GLib::Error &ex) {
      die(ex.what());
    }
  }

  say("Playing " + url);

  // simply local var will do here
  auto loop = GLib::MainLoop::new_();
  Player player(loop, url);

  // schedule start
  GLib::idle_add([&] {
    player.start();
    return GLib::SOURCE_REMOVE_;
  });
  // ... and auto end after a while
  GLib::timeout_add_seconds(10, [&] {
    player.stop();
    return GLib::SOURCE_REMOVE_;
  });

  loop.run();
}