gst.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. #include <functional>
  2. #include <iostream>
  3. #include <memory>
  4. #include <ostream>
  5. #include <sstream>
  6. #include <vector>
  7. #include "assert.h"
  8. //#define GI_INLINE 1
  9. #include <gst/gst.hpp>
  10. namespace GLib = gi::repository::GLib;
  11. namespace Gst = gi::repository::Gst;
  12. namespace GObject_ = gi::repository::GObject;
  13. void
  14. say(const std::string &msg)
  15. {
  16. std::cout << msg << std::endl;
  17. }
  18. void
  19. die(const std::string &why)
  20. {
  21. std::cerr << why << std::endl;
  22. exit(2);
  23. }
  24. class Player
  25. {
  26. typedef Player self;
  27. GLib::MainLoop loop_;
  28. std::string url_;
  29. Gst::Element playbin_;
  30. bool live_ = false;
  31. Gst::Element vfilter_;
  32. Gst::Element afilter_;
  33. std::map<std::string, Gst::Element> filter_;
  34. GLib::SourceScopedConnection monitor_;
  35. GObject_::SignalConnection busconn_;
  36. public:
  37. Player(GLib::MainLoop loop, const std::string &url) : loop_(loop), url_(url)
  38. {
  39. playbin_ = Gst::ElementFactory::make("playbin");
  40. assert(playbin_);
  41. // get a property
  42. // this one is known at introspection time
  43. // (so type is known and suitable checked)
  44. auto name = playbin_.property_name().get();
  45. std::cout << "created playbin " << name << std::endl;
  46. }
  47. void start()
  48. {
  49. // set some property
  50. playbin_.set_property("uri", url_);
  51. // ... or several ones
  52. playbin_.set_properties("volume", 0.5, "mute", false);
  53. // ... non-primitive also possible
  54. vfilter_ = Gst::ElementFactory::make("identity", "vfilter");
  55. afilter_ = Gst::ElementFactory::make("identity", "afilter");
  56. playbin_.set_properties("video-filter", vfilter_, "audio-filter", afilter_);
  57. // connect some; find out what the source element is
  58. // signal not known to introspection, so have to provide signature here
  59. playbin_.connect<void(Gst::Element, Gst::Element)>(
  60. "source-setup", gi::mem_fun(&self::on_source_setup, this));
  61. // for a known signal, it is a bit easier to connect
  62. auto bus = playbin_.get_bus();
  63. bus.add_signal_watch();
  64. // signal connect; using introspected (hence checked) signal definition
  65. // could do without the slot step here and directly pass the lambda,
  66. // but wrapping it this way allows combining this with the returned
  67. // (plain) id into a scoped connection guard
  68. auto slot = bus.signal_message().slot(gi::mem_fun(&self::on_message, this));
  69. busconn_ =
  70. gi::make_connection(bus.signal_message().connect(slot), slot, bus);
  71. // again, if the guard is not desired; the following simply suffices
  72. if (false)
  73. bus.signal_message().connect(gi::mem_fun(&self::on_message, this));
  74. // if the signal signature is somehow not supported
  75. // then the following is a fallback manual method
  76. // which still allows to use lambda (and such)
  77. // and also provides some ownership management
  78. if (false) {
  79. auto h = [](GstBus *, GstMessage *) {
  80. // dummy no-op
  81. };
  82. bus.connect_unchecked<void(GstBus *, GstMessage *)>("message", h);
  83. }
  84. // likewise, if a callback is to be used whose arguments are not supported
  85. // or in a function that is not supported, then the following is a fallback
  86. // which still allows to use lambda (and such)
  87. // and also provides some ownership management
  88. if (false) {
  89. auto h = []() { return false; };
  90. auto cb = new gi::callback_wrapper<gboolean(), false>(h);
  91. // now the above pointer is managed as user data
  92. // and will be suitable destroyed when needed
  93. g_idle_add_full(0, &cb->wrapper, cb, &cb->destroy);
  94. // in case of a typical single-use async callback (with no GDestroyNotify)
  95. // use true as AUTODESTROY template parameter
  96. // (then it will auto-clean up after invoking callback)
  97. }
  98. say("Setting pipeline to PAUSED ...");
  99. auto ret = playbin_.set_state(Gst::State::PAUSED_);
  100. if (ret == Gst::StateChangeReturn::FAILURE_) {
  101. die("Pipeline does not want to pause");
  102. } else if (ret == Gst::StateChangeReturn::NO_PREROLL_) {
  103. say("Pipeline is live and does not need PREROLL ...");
  104. live_ = true;
  105. } else if (ret == Gst::StateChangeReturn::ASYNC_) {
  106. say("Pipeline is PREROLLING ...");
  107. }
  108. // inspect after a while
  109. GLib::timeout_add_seconds(2, [this]() {
  110. inspect();
  111. return GLib::SOURCE_REMOVE_;
  112. });
  113. // some regular progress reporting ...
  114. GLib::SourceFunc func = [this]() {
  115. progress();
  116. return GLib::SOURCE_CONTINUE_;
  117. };
  118. // the introspected function returns a plain id,
  119. // which can be used in the usual way to disconnect
  120. // e.g. at destructor time of owning object
  121. // alternatively, a helper scoped object can take care of that
  122. monitor_ = gi::make_connection(GLib::timeout_add_seconds(1, func), func);
  123. // other such make_connection variations exist;
  124. // e.g. for a signal connection, a probe callback
  125. // (and can easily be custom added)
  126. // add a pad probe; use a casual lambda
  127. // but not too casual, mind (dangling) references/pointers though
  128. filter_["video"] = vfilter_;
  129. filter_["audio"] = afilter_;
  130. for (auto &&p : filter_) {
  131. if (p.second) {
  132. Gst::Pad pad = p.second.get_static_pad("sink");
  133. auto name = p.first;
  134. auto handler = [name](Gst::Pad p, Gst::PadProbeInfo_Ref info) {
  135. auto s = p.get_path_string();
  136. s += "received " + name + " buffer";
  137. auto buffer = info.get_buffer();
  138. if (buffer) {
  139. s += " of size ";
  140. s += std::to_string(buffer.get_size());
  141. }
  142. return Gst::PadProbeReturn::REMOVE_;
  143. };
  144. pad.add_probe(Gst::PadProbeType::BUFFER_, handler);
  145. }
  146. }
  147. // shamelessly demo some helpers that aid in caps/value handling
  148. auto caps = Gst::Caps::new_empty_simple("video/x-raw");
  149. caps.set_value("width", GObject_::Value(Gst::IntRange(240, 320)));
  150. caps.set_value("pixel-aspect-ratio", GObject_::Value(Gst::Fraction(4, 3)));
  151. caps.set_value(
  152. "framerate", GObject_::Value(Gst::FractionRange({25, 1}, 30)));
  153. // retrieving pretty much the same way
  154. auto s = caps.get_structure(0);
  155. auto par = s.get_value("pixel-aspect-ratio").get_value<Gst::Fraction>();
  156. // helpers also stream to string properly
  157. std::ostringstream oss;
  158. oss << par;
  159. oss << (Gst::FlagSet(1, 1) == Gst::FlagSet(2, 1));
  160. // silly test code to exercise some operators
  161. // Rank override allows for succinct numeric conversion
  162. if (+Gst::Rank::PRIMARY_ + 0) {
  163. // flags support various typical operations
  164. (void)(Gst::PadProbeType::BUFFER_ | Gst::PadProbeType::BUFFER_LIST_);
  165. }
  166. }
  167. void stop()
  168. {
  169. if (playbin_)
  170. playbin_.set_state(Gst::State::NULL_);
  171. loop_.quit();
  172. }
  173. void on_message(Gst::Bus /*bus*/, Gst::Message_Ref msg)
  174. {
  175. auto &&src = msg.src_();
  176. switch (msg.type_()) {
  177. case Gst::MessageType::EOS_:
  178. say("Got EOS from " + src.get_path_string());
  179. stop();
  180. break;
  181. case Gst::MessageType::ERROR_: {
  182. GLib::Error err;
  183. gi::cstring debug;
  184. msg.parse_error(&err, &debug);
  185. say("Got error from " + src.get_path_string());
  186. if (debug.size())
  187. say("debug info:\n" + debug);
  188. break;
  189. }
  190. case Gst::MessageType::STATE_CHANGED_:
  191. /* only handle top-level case */
  192. if (src != playbin_)
  193. break;
  194. Gst::State old, new_, pending;
  195. msg.parse_state_changed(&old, &new_, &pending);
  196. if (new_ == Gst::State::PAUSED_ && old == Gst::State::READY_)
  197. playbin_.set_state(Gst::State::PLAYING_);
  198. break;
  199. default:
  200. // never mind
  201. break;
  202. }
  203. }
  204. void on_source_setup(Gst::Element /*pb*/, Gst::Element src)
  205. {
  206. say("source is " + src.get_path_string());
  207. }
  208. void inspect()
  209. {
  210. std::ostringstream oss;
  211. // this should work
  212. auto bin = gi::object_cast<Gst::Bin>(playbin_);
  213. assert(bin);
  214. // a dynamically loaded element may implement a number of interfaces
  215. // which is not known at compile-time
  216. // the cast above can also cast to interface, which can be obtained as
  217. // follows if known at introspection/compile time
  218. auto cp = bin.interface_(gi::interface_tag<Gst::ChildProxy>());
  219. oss << "player bin has " << cp.get_children_count() << " children"
  220. << std::endl;
  221. // get some properties (dynamically, i.e. not known at introspection
  222. // time) type will have to match (i.e. transformable) at runtime
  223. auto n_v = playbin_.get_property<int>("n-video");
  224. auto n_a = playbin_.get_property<int>("n-audio");
  225. // minimal stuff
  226. oss << "sample streams: video=" << n_v << ", audio=" << n_a << std::endl;
  227. // show some tag info
  228. for (auto &&p :
  229. std::map<std::string, int>{{"video", n_v}, {"audio", n_a}}) {
  230. for (int i = 0; i < p.second; ++i) {
  231. // the argument's type should match the signal definition
  232. // (cast if needed to make it so)
  233. auto action = std::string("get-");
  234. action += p.first + "-tags";
  235. auto taglist = playbin_.emit<Gst::TagList>(action, i);
  236. if (!taglist)
  237. continue;
  238. auto ntags = taglist.n_tags();
  239. oss << p.first << " stream " << i << std::endl;
  240. for (int j = 0; j < ntags; ++j) {
  241. auto tname = taglist.nth_tag_name(j);
  242. auto value = taglist.get_value_index(tname, 0);
  243. try {
  244. auto sval = value.transform_value<std::string>();
  245. oss << " " << tname << ": " << sval << std::endl;
  246. } catch (...) {
  247. // could be object or otherwise, never mind
  248. }
  249. }
  250. }
  251. }
  252. // should also have caps here by now
  253. // there are other ways to obtain this, but let's go this way here
  254. for (auto &&p : filter_) {
  255. if (p.second) {
  256. Gst::Pad pad = p.second.get_static_pad("sink");
  257. auto caps = pad.get_current_caps();
  258. oss << p.first << " caps: " << caps.to_string() << std::endl;
  259. }
  260. }
  261. say(oss.str());
  262. say("Playbin elements:");
  263. // Gst::Iterator could be used with native interface
  264. // but a helper wrapper has been provided that supports ease-of-use as
  265. // in ...
  266. for (auto &&el : Gst::IteratorAdapter<Gst::Element>(bin.iterate_recurse()))
  267. say(el.get_path_string());
  268. say("");
  269. }
  270. static std::string time_to_str(Gst::ClockTime time)
  271. {
  272. // could be done otherwise
  273. // but we have native C access at hand, so let's use that
  274. auto s = g_strdup_printf("%" GST_TIME_FORMAT, GST_TIME_ARGS(time));
  275. std::string ret(s);
  276. g_free(s);
  277. return ret;
  278. }
  279. void progress()
  280. {
  281. bool ok = true;
  282. gint64 duration = -1, position = -1;
  283. ok &= playbin_.query_duration(Gst::Format::TIME_, &duration);
  284. ok &= playbin_.query_position(Gst::Format::TIME_, &position);
  285. std::ostringstream oss;
  286. if (ok) {
  287. oss << "Duration: " << time_to_str(duration)
  288. << ", Position: " << time_to_str(position);
  289. } else {
  290. oss << "No progress info available";
  291. }
  292. say(oss.str());
  293. }
  294. };
  295. // not used in the above, but serves as a subclass example
  296. class ChattyBin : public Gst::impl::BinImpl
  297. {
  298. public:
  299. #if 0
  300. // this part is only needed if there is some conflict
  301. // (among members of class and/or interfaces)
  302. // otherwise it should be auto-detected
  303. struct DefinitionData
  304. {
  305. GI_DEFINES_MEMBER(BinClassDef, add_element, true)
  306. };
  307. #endif
  308. ChattyBin() : Gst::impl::BinImpl(this) {}
  309. bool add_element_(Gst::Element element) noexcept override
  310. {
  311. say("adding element " + element.name_() + '\n');
  312. return Gst::impl::BinImpl::add_element_(element);
  313. }
  314. };
  315. int
  316. main(int argc, char **argv)
  317. {
  318. if (argc < 2)
  319. die("missing argument");
  320. // C signature fits C main best anyway
  321. gst_init(&argc, &argv);
  322. std::string url = argv[1];
  323. // make it URL if not so
  324. if (!Gst::Uri::is_valid(url)) {
  325. try {
  326. url = gi::expect(Gst::filename_to_uri(url));
  327. } catch (const GLib::Error &ex) {
  328. die(ex.what());
  329. }
  330. }
  331. say("Playing " + url);
  332. // simply local var will do here
  333. auto loop = GLib::MainLoop::new_();
  334. Player player(loop, url);
  335. // schedule start
  336. GLib::idle_add([&] {
  337. player.start();
  338. return GLib::SOURCE_REMOVE_;
  339. });
  340. // ... and auto end after a while
  341. GLib::timeout_add_seconds(10, [&] {
  342. player.stop();
  343. return GLib::SOURCE_REMOVE_;
  344. });
  345. loop.run();
  346. }