From be0325d0ab8b9da6ee735eab8bf3cf6f9165998a Mon Sep 17 00:00:00 2001 From: Stefan Salewski Date: Wed, 13 Jan 2021 16:50:06 +0100 Subject: [PATCH] added appsink_src.nim gst example --- examples/gtk3/appsink_src.nim | 171 ++++++++++++++++++++++++++++++++++ gintro.nimble | 2 +- gintro/gimplgst.nim | 85 +++++++++++++++++ tests/gen.nim | 18 +++- 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 examples/gtk3/appsink_src.nim create mode 100644 gintro/gimplgst.nim diff --git a/examples/gtk3/appsink_src.nim b/examples/gtk3/appsink_src.nim new file mode 100644 index 0000000..c61bee3 --- /dev/null +++ b/examples/gtk3/appsink_src.nim @@ -0,0 +1,171 @@ +## gcc -Wall appsink-src.c -o appsink-src $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-audio-1.0 gstreamer-app-1.0) +## GStreamer +## +## appsink-src.c: example for using appsink and appsrc. +## +## Copyright (C) 2008 Wim Taymans +## + +import gintro/[gtk, gst, gstapp, gobject, glib] +from std/os import paramCount, paramStr +from std/strutils import `%` + +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) + +## these are the caps we are going to pass through the appsink and appsrc +const + audioCaps = "audio/x-raw,format=S16LE,channels=1,rate=8000, layout=interleaved" + +type + ProgramData = ref object + loop: MainLoop + source: gst.Element + sink: gst.Element + +## called when the appsink notifies us that there is a new buffer ready for processing +proc onNewSampleFromSink(elt: AppSink; data: ProgramData): gst.FlowReturn = + var + sample: gst.Sample + appBuffer: gst.Buffer + buffer: gst.Buffer + source: gst.Element + ## get the sample from appsink + sample = pullSample(cast[AppSink](elt)) + buffer = sample.getBuffer + ## make a copy + appBuffer = buffer.copy() + ## we don't need the appsink sample anymore + # gst_sample_unref(sample) + ## get source an push new buffer + source = getByName(cast[gst.Bin](data.sink), "testsource") + result = pushBuffer(cast[AppSrc](source), appBuffer) + # gst_object_unref(source) + +## called when we get a GstMessage from the source pipeline when we get EOS, we notify the appsrc of it. +#BusFunc* = proc (bus: ptr Bus00; message: ptr Message00; userData: pointer): gboolean {.cdecl.} +proc onSourceMessage(bus: gst.Bus; message: gst.Message; data: ProgramData): bool = + var source: gst.Element + var h = message.impl.type + if h == {MessageFlag.eos}: + echo("The source got dry") + source = getByName(cast[gst.Bin](data.sink), "testsource") + discard endOfStream(cast[AppSrc](source)) + # gst_object_unref(source) + elif h == {MessageFlag.error}: + echo("Received error") + glib.quit(data.loop) + else: + discard + return true + +## called when we get a GstMessage from the sink pipeline when we get EOS, we +## exit the mainloop and this testapp. +proc onSinkMessage(bus: gst.Bus; message: gst.Message; data: ProgramData): bool = + var h = message.impl.type + if h == {MessageFlag.eos}: + echo("Finished playback") + glib.quit(data.loop) + elif h == {MessageFlag.error}: + echo("Received error") + glib.quit(data.loop) + else: + discard + return true + +proc main = + ## Parse arguments + let argc = paramCount() + 1 + var argv = newSeq[string](argc) + for i in 0 ..< argc: + argv[i] = paramStr(i) + + var + filename: string + data: ProgramData + str: string + bus: gst.Bus + testsink: gstApp.AppSink + testsource: gst.Element + + gst.init() + if argc == 2: + filename = argv[1] + else: + filename = "/usr/share/sounds/ekiga/ring.wav" + if not fileTest(filename, FileTest.exists): + echo("File $1 does not exist" % [filename]) + # g_free(filename) + quit(QuitFailure) + data = new ProgramData #g_new0(ProgramData, 1) + data.loop = glib.newMainLoop(nil, false) + ## setting up source pipeline, we read from a file and convert to our desired caps. + str = "filesrc location=\"$1\" ! wavparse ! audioconvert ! audioresample ! appsink caps=\"$2\" name=testsink" % [filename, audioCaps] + #g_free(filename) + data.source = gst.parseLaunch(str) #, nil) + #g_free(string) + if data.source == nil: + echo("Bad source") + # g_main_loop_unref(data.loop) + # g_free(data) + quit(QuitFailure) + bus = gst.getBus(data.source) + gst.addWatch(bus, glib.PRIORITY_DEFAULT.int, onSourceMessage, data) + # gst_object_unref(bus) + ## we use appsink in push mode, it sends us a signal when data is available + ## and we pull out the data in the signal callback. We want the appsink to + ## push as fast as it can, hence the sync=false + testsink = cast[AppSink](getByName(cast[gst.Bin](data.source), "testsink")) + # g_object_set(G_OBJECT(testsink), "emit-signals", TRUE, "sync", FALSE, nil) + setProperty(testsink, "emit-signals", toBoolVal(true)) + setProperty(testsink, "sync", toBoolVal(false)) + connect(testsink, "new-sample", onNewSampleFromSink, data) + #gst_object_unref(testsink) + ## setting up sink pipeline, we push audio data into this pipeline that will + ## then play it back using the default audio sink. We have no blocking + ## behaviour on the src which means that we will push the entire file into + ## memory. + str = "appsrc name=testsource caps=\"$1\" ! autoaudiosink" % [audioCaps] + data.sink = gst.parseLaunch(str) # , nil) was GError + #g_free(string) + if data.sink == nil: + echo("Bad sink") + #gst_object_unref(data.source) + #g_main_loop_unref(data.loop) + #g_free(data) + quit(QuitFailure) + testsource = gst.getByName(cast[gst.Bin](data.sink), "testsource") + ## configure for time-based format + setProperty(testsource, "format", toUIntVal(Format.time.ord)) # caution, is the enum signed or unsigned? + #g_object_set(testsource, "format", GST_FORMAT_TIME, nil) + ## uncomment the next line to block when appsrc has buffered enough + ## g_object_set (testsource, "block", TRUE, NULL); + #gst_object_unref(testsource) + bus = getBus(data.sink) + addWatch(bus, glib.PRIORITY_DEFAULT.int, onSinkMessage, data) + #gst_object_unref(bus) + ## launching things + discard setState(data.sink, State.playing) + discard setState(data.source, State.playing) + ## let's run !, this loop will quit when the sink pipeline goes EOS or when an + ## error occurs in the source or sink pipelines. + echo("Let\'s run!") + glib.run(data.loop) + echo("Going out") + discard setState(data.source, State.null) + discard setState(data.sink, State.null) + # gst_object_unref(data.source) + # gst_object_unref(data.sink) + # g_main_loop_unref(data.loop) + # g_free(data) + quit(QuitSuccess) + +main() + diff --git a/gintro.nimble b/gintro.nimble index 350d13a..83100ff 100644 --- a/gintro.nimble +++ b/gintro.nimble @@ -1,6 +1,6 @@ # Package -version = "0.8.5" +version = "0.8.6" author = "Stefan Salewski" description = "High level GObject-Introspection based GTK3/GTK4 bindings" license = "MIT" diff --git a/gintro/gimplgst.nim b/gintro/gimplgst.nim new file mode 100644 index 0000000..bc79bd1 --- /dev/null +++ b/gintro/gimplgst.nim @@ -0,0 +1,85 @@ +import macros +from strutils import `%` + +# mostly a copy of gimplglib.nim +########################################################## +#proc gst_bus_add_watch_full(self: ptr Bus00; priority: int32; `func`: BusFunc; +# userData: pointer; notify: DestroyNotify): uint32 {. +# importc, libprag.} + +#guint gst_bus_add_watch (GstBus *bus, GstBusFunc func, gpointer user_data); +#guint gst_bus_add_watch_full (GstBus *bus, gint priority, GstBusFunc func, gpointer user_data, GDestroyNotify notify); + +#BusFunc* = proc (bus: ptr Bus00; message: ptr Message00; userData: pointer): gboolean {.cdecl.} + +macro mAddWatchFull(bus: gst.Bus; pri: cint; p: untyped; arg: typed; ignoreArg: bool): untyped = + var IdleID {.compileTime, global.}: int + inc(IdleID) + let ats = $getTypeInst(arg).toStrLit + let procName = "gstfunc_" & $IdleID + let procNameCdecl = "gstfunc_cdecl_" & $IdleID + + var r1s = """ + +proc $1(self: ptr gst.Bus00; msg: ptr gst.Message00; userData: pointer): gboolean {.cdecl.} = + var bus1 {.global.}: gst.Bus + if bus1.isNil: + new bus1 + GC_ref(bus1) # never call destroy/finalizer on this global variable + bus1.ignoreFinalizer = true + bus1.impl = self + + var msg1 {.global.}: gst.Message + if msg1.isNil: + new msg1 + GC_ref(msg1) # never call destroy/finalizer on this global variable + msg1.ignoreFinalizer = true + msg1.impl = msg + + $2(bus1, msg1""" + + if not ignoreArg.boolVal: + if getTypeInst(arg).typeKind == ntyRef: + r1s.add(", cast[$3](userdata)") + else: + r1s.add(", cast[$3](userdata)") + r1s.add(").gboolean\n") + + r1s = r1s % [$procNameCdecl, $p, ats] + + let r2s = + if ignoreArg.boolVal: + + """ +proc $1(self: gst.Bus; pri: cint): cuint {.discardable.} = + gst_bus_add_watch_full(cast[ptr gst.Bus00](self.impl), pri, $5, nil, nil) + +$1($5, $3, $4) +""" % [$procName, ats, $pri, "cond", $procNameCdecl, $(arg.toStrLit), $(bus.toStrLit)] + + else: + + """ +proc $1(self: gst.Bus; pri: cint; a: $2): cuint {.discardable.} = + when (a is ref object): + GC_ref(a) + return gst_bus_add_watch_full(cast[ptr gst.Bus00](self.impl), pri, $5, cast[pointer](a), nil) + else: + var ar: ref $2 + new(ar) + #deepCopy(ar[], a) + ar[] = a + GC_ref(ar) + return gst_bus_add_watch_full(cast[ptr gst.Bus00](self.impl), pri, $5, cast[pointer](ar[]), nil) +$1($7, $3, $6) +""" % [$procName, ats, $(pri.toStrLit), "fixedCond", $procNameCdecl, $(arg.toStrLit), $(bus.toStrLit)] + + #echo r1s + #echo r2s + result = parseStmt(r1s & r2s) + +template addWatch*(bus: gst.Bus; pri: int; p: untyped; arg: typed): untyped = + maddWatchFull(bus, pri.cint, p, arg, false) + +template addWatch*(bus: gst.Bus; pri: int; p: untyped): untyped = + maddWatchFull(bus, pri.cint, p, "", true) diff --git a/tests/gen.nim b/tests/gen.nim index cb6c8ae..93bc830 100644 --- a/tests/gen.nim +++ b/tests/gen.nim @@ -1,6 +1,6 @@ # High level gobject-introspection based GTK3/GTK4 bindings for the Nim programming language # nimpretty --maxLineLen:130 gen.nim -# v 0.8.6 2021-JAN-11 +# v 0.8.6 2021-JAN-13 # (c) S. Salewski 2018 # usefull for finding death code: @@ -1421,7 +1421,7 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = # export only low level symbols that are needed internally -- maybe we should export only a private name for them if run == 0: if sym in ["g_quark_from_static_string", "g_error_free", "g_object_get_qdata", "g_object_ref_sink", "g_object_unref", - "g_timeout_add_full", "g_object_is_floating", + "g_timeout_add_full", "g_object_is_floating", "gst_bus_add_watch_full", "g_type_from_name", "g_type_check_instance_is_a", "g_idle_add_full", "g_quark_try_string", "vte_regex_unref", "gtk_builder_get_object", "g_action_map_add_action", "gtk_drawing_area_set_draw_func", "g_io_add_watch_full"]: methodBuffer.write("\nproc " & sym & EM & pars.plist) @@ -2942,6 +2942,17 @@ proc fromStringStructure*(string: cstring; `end`: var string): Structure = result.impl = impl0 `end` = $(end_00) +# https://discourse.gnome.org/t/gst-buffer-copy-is-missing-in-gir-file/5327 +# GstMiniObject * gst_mini_object_copy (const GstMiniObject *mini_object); +proc gst_buffer_copy(self: ptr Buffer00): ptr Buffer00 {. + importc: "gst_mini_object_copy", libprag.} + +proc copy*(self: Buffer): Buffer = + fnew(result, gBoxedFreeGstBuffer) + result.impl = gst_buffer_copy(cast[ptr Buffer00](self.impl)) + +include gimplgst + """ const Pango_EPI = """ @@ -3590,6 +3601,7 @@ proc init* = output.write("include gimplgobj\n") output.write("include gimplgtk\n") buildableList.add("MenuModel") + buildableList.add("FileChooserNative") for i in buildableList: var prefix = "gtk" var modname = fixedModName(moduleNamespace) @@ -3808,7 +3820,7 @@ launch() # if not xcallerAlloc.contains(el): # echo el -# 3741 lines Extern import writeConst gBaseInfoGetNamespace atk writeStruct attribute destroy writeMethode builder +# 3741 lines Extern import writeConst gBaseInfoGetNamespace atk writeStruct attribute destroy writeMethode builder include EM # gtk_icon_view_get_tooltip_context bug Candidate # gtk_tree_view_get_cursor bug #