GstPlaySignalAdapter error callback passes NULL pointer as extra data

I’m trying to build a media player using GstPlay and am having trouble implementing the error signal callback. Other callbacks are working fine when I pass a class’s this pointer to the callback as extra data, but the error callback receives NULL instead. This is all taken from the gst-play.c example. I wondered if this was some kind of scoping issue with the way I’m wrapping things in C++ but the state-changed callback fires afterwards and receives a valid pointer to the instance. Any ideas what might be going wrong or what I’ve missed?

#include <csignal>
#include <memory>
#include <string>

#include <gst/gst.h>
#include <gst/play/play.h>

struct Result {
    bool is_error;
    std::string description;

    Result() = delete;
    operator bool() { return !is_error; }

    static Result ok() { return {false, ""}; }
    static Result error(std::string description) { return {true, description}; }
};

class App {
public:
    App() :
        loop_(nullptr),
        player_(nullptr),
        signal_adapter_(nullptr)
    {}

    App(App&) = delete;

    Result init() {
        player_ = gst_play_new(NULL);
        if (!player_) {
            return Result::error("failed to initialise GstPlay object");
        }
        loop_ = g_main_loop_new(NULL, FALSE);
        if (!loop_) {
            return Result::error("failed to initialise GMainLoop object");
        }
        signal_adapter_ = gst_play_signal_adapter_new(player_);
        if (!signal_adapter_) {
            return Result::error("failed to initialise GstPlaySignalAdapter object");
        }

        g_signal_connect(signal_adapter_, "end-of-stream", G_CALLBACK(end_of_stream_cb), this);
        g_signal_connect(signal_adapter_, "error", G_CALLBACK(error_cb), this);
        g_signal_connect(signal_adapter_, "state-changed", G_CALLBACK(state_changed_cb), this);
        g_signal_connect(signal_adapter_, "buffering", G_CALLBACK(buffering_cb), this);

        return Result::ok();
    }

    void set_uri(std::string uri) {
        g_object_set(G_OBJECT(player_), "uri", uri.c_str(), (void*)0);
    }

    void run() {
        gst_play_play(player_);
        g_main_loop_run(loop_);
    }

    void stop() {
        g_main_loop_quit(loop_);
    }

    ~App() {
        if (signal_adapter_) {
            g_clear_object(&signal_adapter_);
        }

        if (player_) {
            gst_object_unref(player_);
        }

        if (loop_) {
            g_main_loop_unref(loop_);
        }
    }

private:
    static void end_of_stream_cb(GstPlaySignalAdapter* adapter, App* app) {
        gst_print("EOS\n");
        app->stop();
    }

    static void error_cb(GstPlaySignalAdapter* adapter, GError* err, App* app) {
        gst_printerr("ERROR: %s, %p\n", err->message, app);
        if(app) {
            app->stop();
        } else {
            gst_printerr("ERROR: cannot access app to stop it\n");
        }
    }

    static void state_changed_cb(GstPlaySignalAdapter* adapter, GstPlayState state, App* app) {
        gst_print("State changed: %s, %p\n", gst_play_state_get_name(state), app);
    }

    static void buffering_cb(GstPlaySignalAdapter* adapter, gint percent, App* app) {
        gst_print("Buffering: %d\n", percent);
    }

    GMainLoop* loop_;
    GstPlay* player_;
    GstPlaySignalAdapter* signal_adapter_;

};

static App* global_app_ptr;
void sigint_handler(int) {
    if(global_app_ptr) {
        global_app_ptr->stop();
    }
}

int main (int argc, char** argv) {
    gst_init(&argc, &argv);

    if (argc != 1) {
        g_printerr("WARNING: this command takes no arguments\n");
    }

    App app;
    global_app_ptr = &app;
    std::signal(SIGINT, sigint_handler);

    Result result = app.init();
    if (!result) {
        g_printerr("ERROR: %s\n", result.description.c_str());
        return 1;
    }

    app.set_uri("file:///missing.wav");

    app.run();

    g_print("Done\n");

    return 0;
}

This outputs the following:

State changed: buffering, 0000002F518FF8B0

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.011: gst_structure_copy: assertion 'structure != NULL' failed      

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.011: structure_serialize: assertion 'structure != NULL' failed     

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.012: structure_serialize: assertion 'structure != NULL' failed     

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.012: gst_structure_free: assertion 'structure != NULL' failed      

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.013: structure_serialize: assertion 'structure != NULL' failed     

(scl6play.exe:19340): GStreamer-CRITICAL **: 13:41:51.015: structure_serialize: assertion 'structure != NULL' failed     
ERROR: Failed to play, 0000000000000000
ERROR: cannot access app to stop it
State changed: stopped, 0000002F518FF8B0
Done

Having built the example gst-play.c on its own, that too exhibits the same behaviour, so either the example is doing something wrong, or there’s a bug somewhere. I’d assumed that the gst-play-1.0 binary would have been built from this example, but perhaps there’s a different version somewhere…

Simple enough in the end, RTFM. The signature for the error callback should be

static void error_cb(GstPlaySignalAdapter* adapter, GError* err, GstStructure* details, App* app)

The example is missing the GstStructure* details argument. Still not sure why the CRITICAL warnings are being emitted though.

The assertion failures are caused by setting the uri property. It also happens when using gst_play_set_uri so this looks like a bug.

This sounds like play: Fix a critical warning in error callback (!6385) · Merge requests · GStreamer / gstreamer · GitLab

Can you submit a MR with the fix for the example? :slight_smile:

Ah, I confused myself by looking at a freshly checked out gstreamer repo which already had that change in it! In which case that probably has fixed that, I’m just linking to an older version of the library.

I’ll see about submitting a merge request for the example. I suspect the warning callback has the same issue!

1 Like