gintro icon indicating copy to clipboard operation
gintro copied to clipboard

[Question] GST: getting the source's name from Message

Open mantielero opened this issue 2 years ago • 6 comments

The type from Message.impl.src is ptr Object00 but I cannot feed that to name: proc name*(self: Object): string

Is there a way to convert ptr Object00 into Object?

type
  Object* = ref object of gobject.InitiallyUnowned
  Object00* = object of gobject.InitiallyUnowned00

mantielero avatar Jan 09 '22 11:01 mantielero

As you may know, I do know absolutely nothing about the gstreamer library. And I would generally recommend not to use it with gintro. GStreamer is a very low level library, so it is really hard to use it with wrappers or bindings. It may be possible, but I guess using C or Rust is much easier. For your question, using Google and grep gives

grep getStructure ~/.nimble/pkgs/gintro-0.9.6/gintro/gst.nim 
proc getStructure*(self: Message): Structure =

grep getName ~/.nimble/pkgs/gintro-0.9.6/gintro/gst.nim 
proc getName*(self: Structure): string =

So there seems to exist a function which can give you a Structure type from a Message, and there is a function getName() which seems to return a string from a Structure. So that may be what you need, but of course that is fully untested, and maybe I misunderstand you.

StefanSalewski avatar Jan 09 '22 12:01 StefanSalewski

Thanks Stefan for your answer, but I explained myself poorly. Your response is in order to get Message's name. But I am aiming to get source's name:

type
  Message00* {.pure.} = object
    miniObject*: MiniObject00
    `type`*: MessageType
    timestamp*: uint64
    src*: ptr Object00                   #<------------------------ This name
    seqnum*: uint32
    lock*: glib.Mutex
    cond*: glib.Cond
  Message* = ref object
    impl*: ptr Message00
    ignoreFinalizer*: bool

If there were a getObject.

What I did as a way around was making the following function public:

proc gst_object_get_name*(self: ptr Object00): cstring 

mantielero avatar Jan 09 '22 12:01 mantielero

I think your explanation was fine, the problem is that I do know nothing about gst and that gst is so difficult. Indeed gst is nearly the only GTK related lib where we have to export the fields of some objects. GTK2 did that for a few types, but with GTK3 we have generally getter and setter functions for the field content. So your idea of using gst_object_get_name() is a solution indeed. There may exist better solutions, we would have to discuss that with a gst expert, maybe with Mr. Droege. But for that we would need a complete working app, or better a minimal working example, so that he can really easily understand what we intent. Or we would have to find some existing, working C code, which we can convert to Nim.

StefanSalewski avatar Jan 09 '22 15:01 StefanSalewski

My objective is to through the GST Basic Tutorials.

Right now I am on 3: Dynamic Pipelines which I am trying to translate into Nim. In particular in the part where error's are checked.

My workaround is in line:

echo &"Error received from element {gst_object_get_name(msg.impl.src)}: {err.message}" 

But I think it would be better a mean to get an Object from a ptr Object00 (here I am talking about gst's Object and Object00, not about the ones in gobject).

My still crappy code (mixing C and nim): ex03_dynamic_pipelines.nim

import gintro/[gst,gobject,glib], os, strformat

# Structure to contain all our information, so we can pass it to callbacks 
type
  CustomData = object
    pipeline, source, convert, resample, sink: gst.Element 

#[
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;
]#

#[
/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
]#


#[ proc sourceName*(self: Message): string =
  let resul0 = gst_object_get_name(cast[ptr Object00](self.impl.src))
  if resul0.isNil:
    return
  result = $resul0
  cogfree(resul0) ]#


# This function will be called by the pad-added signal NOTA: cast[ptr Pad00](pad.impl)
proc padAddedHandler(src:ptr Element00; newPad:ptr Pad00; dataPtr:pointer) {.cdecl.} =
  var data = cast[CustomData](dataPtr)
  #var sinkPad:gst.Pad = gst_element_get_static_pad(data.convert, "sink")
  var sinkPad:gst.Pad = getRequestPad(data.convert, "sink")
  #GstPadLinkReturn ret;
  #GstCaps *new_pad_caps = NULL;
  #GstStructure *new_pad_struct = NULL;
  #const gchar *new_pad_type = NULL;
  echo "New pad"
  #echo("Received new pad '%s' from '%s':\n",  newPad.name,  src.name )  # GST_PAD_NAME, GST_ELEMENT_NAME

  #[ /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  } ]#

 #[  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
    goto exit;
  } ]#

 #[  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }
 ]#
#[ exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad); ]#




proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc toUIntVal(i: int): Value =
  let gtype = typeFromName("guint")
  discard init(result, gtype)
  setUint(result, i)

proc toStringVal(i: string): Value =
  let gtype = typeFromName("gchararray")
  discard init(result, gtype)
  setString(result, i)
  echo repr result

converter toBin*(elem:gst.Element):gst.Bin =
  cast[gst.Bin](elem)

proc main =
  var data:CustomData
  #GstBus *bus;
  #GstMessage *msg;
  #GstStateChangeReturn ret;
  #gboolean terminate = FALSE;

  ##  Initialize GStreamer
  gst.init() 

  # Create the elements
  data.source   = make("uridecodebin", "source")
  data.convert  = make("audioconvert", "convert")
  data.resample = make("audioresample", "resample")
  data.sink     = make("autoaudiosink", "sink")

  # Create the empty pipeline
  data.pipeline = newPipeline("test-pipeline")

  # Check if the elements are empty
  if data.source.isNil or data.pipeline.isNil or data.convert.isNil or data.resample.isNil or data.sink.isNil:
    quit("Not all elements could be created.\n") # QuitFailure, 

  # Build the pipeline 
  # 1. Add the elements
  discard data.pipeline.add(data.source)
  discard data.pipeline.add(data.convert)
  discard data.pipeline.add(data.resample)
  discard data.pipeline.add(data.sink)

  # 2. Linking
  discard data.source.link(data.convert)  
  discard data.convert.link(data.resample)
  discard data.resample.link(data.sink)

  #[
  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
  if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }
  ]#

  # Set the URI to play
  var uri = toStringVal("https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm")
  data.source.setProperty("uri", uri)

  # Connect to the pad-added signal
  #g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
  #var flag:ConnectFlag = 0
  #discard scPadAdded( data.source, padAddedHandler, cast[pointer](data.unsafeAddr), {0.ConnectFlag})


  # Start playing
  discard gst.setState(data.pipeline, gst.State.playing) 


  ##  Wait until error or EOS
  var bus:gst.Bus     = gst.getBus(data.pipeline)
  var msg:gst.Message = gst.timedPopFiltered(bus, gst.Clock_Time_None, {gst.MessageFlag.error, gst.MessageFlag.eos})
  var tmp = msg.getType
  discard gst.setState(data.pipeline, gst.State.null) # is this necessary?

  #var msgFlag:MessageFlag = msg.getType
  var msgType = msg.getType
  #echo msg.impl.src.getName
  #echo msg.getStructure.getName

  if gst.MessageFlag.error in msgType:
    var err:ptr glib.Error
    var debugInfo:string
    msg.parseError(err, debugInfo)

    #echo typeof(msg.src)
    echo &"Error received from element {gst_object_get_name(msg.impl.src)}: {err.message}"   #<============== HERE
    #echo cast[ptr gst.Object00](msg.impl).name
    if debugInfo == "":
      debugInfo = "none"
    echo &"Debugging information: {debugInfo}\n"
    #echo "Error received from element %s: %s", msg.src.name, err.message

#[


  /* Listen to the bus */
  bus = gst_element_get_bus (data.pipeline);
  do {
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* Parse message */
    if (msg != NULL) {
      GError *err;
      gchar *debug_info;

      switch (GST_MESSAGE_TYPE (msg)) {
        case GST_MESSAGE_ERROR:
          gst_message_parse_error (msg, &err, &debug_info);
          g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
          g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
          g_clear_error (&err);
          g_free (debug_info);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  gst_object_unref (data.pipeline);
  return 0;
}


]#
main()

My first issue (raised here is looking into Message's content). But there are others (I don't know why the pipeline is not working).

mantielero avatar Jan 09 '22 19:01 mantielero

I managed doing what @mantielero wanted to achive. To extract the Message source and name I added two procedures into the gst.nim source code. This maybe can be done in another way.

# Those procedures should be added to 'gst.nim'
proc src*(self: Message): ptr Object00 =
  var srcElement: ptr gst.Object00 = self[].impl[].src
  return srcElement

proc name*(self: ptr Object00): string =
  let resul0 = gst_object_get_name(self)
  if resul0.isNil:
    return
  result = $resul0
  cogfree(resul0)

The example n.3 of gstreamer in nim: https://gstreamer.freedesktop.org/documentation/tutorials/basic/dynamic-pipelines.html?gi-language=c

import gintro/gst
import gintro/gobject
import strformat


## Working example with a dynamic pipeline

var source, convert, resample, sink: Element

proc padAdded(self: ptr Element00; newPad: ptr Pad00; xdata: pointer) {.cdecl} =
  # is called twice, first time link fails
  var name: Value
  let gtype = typeFromName("gchararray")
  discard gobject.init(name, gtype)
  source.getProperty("name", name)

  if source.link(convert):
    echo fmt"New pad added for element {name.getString()}"

proc main =
  #  Initialize GStreamer
  gst.init()
  # create elements
  source = gst.make("uridecodebin", "source")
  convert = gst.make("audioconvert", "convert")
  resample = gst.make("audioresample", "resample")
  sink = gst.make("autoaudiosink", "sink")
  assert not source.isNil and not convert.isNil and not resample.isNil and not sink.isNil, "Could not create all elements"
  var pipeline = gst.newPipeline("mypipeline")
  var bus = gst.getBus(pipeline)
  bus.addSignalWatch()
  bus.enableSyncMessageEmission()

  assert pipeline.add(source) and pipeline.add(convert) and pipeline.add(resample) and pipeline.add(sink), "Cannot add elements to pipeline"

  # link elements
  assert link(convert, resample) and link(resample, sink), "Cannot link"
  var v: Value
  let gtype = typeFromName("gchararray")
  discard gobject.init(v, gtype)
  v.setString("https://gstreamer.freedesktop.org/media/sintel_trailer-480p.webm")
  source.setProperty("uri", v)
  var cf: ConnectFlags
  echo scPadAdded(source, padAdded, nil, cf)
  ##  Start playing
  discard gst.setState(pipeline, gst.State.playing)
  echo "waiting for messages"
  while true:
    var msg = gst.timedPopFiltered(bus, gst.Clock_Time_None,
      {gst.MessageFlag.error, gst.MessageFlag.eos, gst.MessageFlag.info, gst.MessageFlag.stateChanged, gst.MessageFlag.buffering})
    var kind = msg.getType.getName()
    echo "Object kind:", kind, " name:", msg.src.name()

    if kind == "eos":
      echo "End of stream"
      break

main()

piertoni avatar Mar 31 '23 16:03 piertoni

I managed doing what @mantielero wanted to achive.

Great!

StefanSalewski avatar Apr 16 '23 17:04 StefanSalewski