Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mantielero opened this issue Jan 9, 2022 · 6 comments
Open

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

mantielero opened this issue Jan 9, 2022 · 6 comments

Comments

@mantielero
Copy link

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
@StefanSalewski
Copy link
Owner

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.

@mantielero
Copy link
Author

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 

@StefanSalewski
Copy link
Owner

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.

@mantielero
Copy link
Author

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).

@piertoni
Copy link

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()

@StefanSalewski
Copy link
Owner

I managed doing what @mantielero wanted to achive.

Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants