| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- #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();
- }
|