Porting CLI to Go

I need help porting this command line to Go…

gst-launch-1.0 \
    videotestsrc is-live=1 \
    ! videoconvert \
    ! "video/x-raw, width=1920, height=1080, framerate=25/1" \
    ! queue \
    ! x264enc cabac=1 bframes=2 ref=1 \
    ! "video/x-h264,profile=main" \
    ! flvmux streamable=true name=mux \
    ! rtmpsink location="${RTMP_DEST} live=1" \
    audiotestsrc is-live=1 wave=ticks \
    ! voaacenc bitrate=128000 \
    ! mux.

All I have so far is this…

	gst.Init()
	pipeline := gst.NewPipeline("").(gst.Pipeline)

	videotestsrc := gst.ElementFactoryMake("videotestsrc", "")
	videoconvert := gst.ElementFactoryMake("videoconvert", "")
	video_x_raw := gst.ElementFactoryMake("video/x-raw", "")
	queue := gst.ElementFactoryMake("queue", "")
	x264enc := gst.ElementFactoryMake("x264enc", "")
	video_x_h264 := gst.ElementFactoryMake("video/x-h264", "")
	flvmux := gst.ElementFactoryMake("flvmux", "")
	rtmpsink := gst.ElementFactoryMake("rtmpsink", "")
	audiotestsrc := gst.ElementFactoryMake("audiotestsrc", "")
	voaacenc := gst.ElementFactoryMake("voaacenc", "")

	pipeline.AddMany(videotestsrc, videoconvert, video_x_raw, queue, x264enc,
		video_x_h264, flvmux, rtmpsink, audiotestsrc, voaacenc, flvmux)

	gst.LinkMany(videotestsrc, videoconvert, video_x_raw, queue, x264enc,
		video_x_h264, flvmux, rtmpsink, audiotestsrc, voaacenc, flvmux)

	// TODO: add arguments to elements

but I don’t think this is the way to handle the mux. name, and I’m not confident my entire approach is correct.

Help and advice will be most welcome.

@RSWilli - any thoughts?

This is exactly how the name “mux.” is handled under the hood in gst-launch-1.0

The problem instead is the “video/x-h264”. The factory name / element name to use is capsfilter. If you read the docs you can see that the syntax used in gst-launch is just a shorthand for setting the caps property of the capsfilter element.

So

"video/x-h264,profile=main"

is the same as

capsfilter caps="video/x-h264,profile=main"

Which should make it clear that you need to construct the capsfilter element and then set the caps property on it.

@RSWilli - GStreamer is far too convoluted for my tiny brain. Nevertheless, I’m committed to use it, so I thought I’d try your go-gst example launch.

This works with the official GStreamer CLI,

gst-launch-1.0 \
    videotestsrc is-live=1 \
    ! videoconvert \
    ! "video/x-raw, width=1920, height=1080, framerate=25/1" \
    ! queue \
    ! x264enc cabac=1 bframes=2 ref=1 \
    ! "video/x-h264,profile=main" \
    ! flvmux streamable=true name=mux \
    ! rtmpsink location="rtmp://somewhere/live/etc live=1" \
    audiotestsrc is-live=1 wave=ticks \
    ! voaacenc bitrate=128000 \
    ! mux.

but go-gst example launch errors with this…

Parse error: no property "live" in element "rtmpsink"

I’m still studying your reply, and I don’t understand why some things need quotes & commas, when others don’t. Same with live & is-live. More convolution and too many plugins. Of course, I don’t blame you for any of this nonsense.

I don’t understand why some things need quotes & commas, when others don’t

gst-launch has a special syntax that needs to be compatible with bash, and bash only knows strings and has some weird rules with space splitting.

Essentially for gst-launch you need to use a quoted string when the value contains a space. (other quotes are optional). The error you are seeing is because you tried to set the property “live” on the rtmpsink, when it only has a property named “location”. The live=1 is not a separate property of the element, but instead part of the string value of the “location” property.

Regarding the error from the go example: this is caused by this line, where the arguments are joined by a space, which creates a problem when the original value already contained a space. I’m not quite sure how to handle this correctly though.

If you find a way then feel free to create a PR :slight_smile:

@RSWilli

Stuck again…

videotestsrc = gst.ElementFactoryMake("videotestsrc", "")

videotestsrc.SetObjectProperty("is-live=1", <what-goes-here?>)

Edit: perhaps?

videotestsrc.SetObjectProperty(“is-live”, 1)

So if more than one property, then one line per property?

@RSWilli - I’m completely lost, so I’ve uploaded TryRTMP to GitHub and ask if you could take a look?

The following are different than in your gst-launch-1.0 call:

  • TryRTMP/main.go at main · ea7kir/TryRTMP · GitHub : The rtmpsink.SetObjectProperty("location", "rtmp://somewhere.any/live/name live=1") as said above, the live=1 is part of the location property (because it is quoted) live is not a different property.
  • videotestsrc.SetObjectProperty(“is-live”, 1) looks better, but the is-live property on videotestsrc takes in a gboolean which is a bool in go, so videotestsrc.SetObjectProperty(“is-live”, true) would be correct.
  • TryRTMP/main.go at main · ea7kir/TryRTMP · GitHub the caps property on capsfilter takes GstCaps or the go equivalent gst.Caps as value. The string you used in your gst-launch command can be converted to such an object via the gst_caps_from_string aka caps := gst.CapsFromString("video/x-raw, width=1920, height=1080, framerate=25/1") and then capsfilter1.SetObjectProperty(“caps”, caps).

Some general tips:

  • if you’re using an element, try to search for it in the gstreamer docs and look at the type of the properties (sometimes this is tricky because of inheritance)
  • There is also go-gst/pkg/gst/element_factory.go at generated_bindings · go-gst/go-gst · GitHub for your convinience which allows you to create an element and directly set properties. This makes it a bit easier when you have to set a lot of properties

I’ve updated the repro for you to look at, with only 2 problems that I see.

  1. How do I declare GstAudioTestSrcWave.ticks, so I don’t have to use an integer, 8 in my case.
  2. Run time fatal: pipeline.AddMany returned false.

regarding 1.: Gstreamer provides an additional method to set properties via their name, and this function is bound in go as gst.UtilSetObjectArg. The value string will have the same syntax as in gst-launch

regarding 2.: this is heading into a space where I cannot help you much anymore. I’d try to use pipeline.Add to figure out which element exactly is failing to be added.

I’ve also found another bug, you are calling pipeline.LinkMany which will fail, because this call will link all elements in the order they were passed. Your gst-launch pipeline is linked differently. You need to call Link (or LinkMany) for each “!” in your gst-launch call.

This is really bad news for me, because it seems no other Go developers are following this. Without your help, I fear my larger project, will grind to a halt.

It turns out 2 elements are failing to Add. capsfilter2 and the final flvmux name, even though capsfilter1 appears to be fine. I’ve updated the repro to reflect this, but haven’t edited the LinkMany just yet.

If TryRTMP ever gets to work, and tidied up, it could become a go-gst example.

Please try to run it with the env var “GST_DEBUG=2” set (Windows syntax may differ). You’ll see what’s going on there. This may be a bug in the generated bindings. The only reason I can think of why the elements are not accepted is because they have non unique names. Normally though, an empty name should choose a unique one automatically.

Your assumption is correct.

0:00:00.012854530 200907     0x31beabd0 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'capsfilter1' is not unique in bin, not adding
2025/06/05 20:04:29 main.go:158: TryRTMP failed to add capsfilter2
0:00:00.012872177 200907     0x31beabd0 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'flvmux0' is not unique in bin, not adding
2025/06/05 20:04:29 main.go:167: TryRTMP failed to add flvmux

The second argument for the elementfactory is the name of the element. You can choose your own, but it not choosing a unique name automatically is a bug somewhere.

Sure, I can name an element, but I don’t see a way to use it.

but I don’t see a way to use it.

Why? Please elaborate. If you choose unique names then adding them to the pipeline should not fail.

I’m tried various ways, but have same outcome.

0:00:00.018618643 214637     0x21fdbb00 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'capsfilter2' is not unique in bin, not adding
2025/06/06 13:58:25 main.go:158: TryRTMP failed to add capsfilter2
0:00:00.018656667 214637     0x21fdbb00 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'flvmux' is not unique in bin, not adding
2025/06/06 14:00:33 main.go:167: TryRTMP failed to add flvmux

@RSWilli and anyone else who can help with an updated version of TryRTMP at June 7 - 15:49

Line 125 contains an if statement to help while testing.

1. using pipeline.Add

$ go run .
2025/06/07 14:11:55 main.go:256: TryRTMP -> rtmp://somewhere.any/live/name
2025/06/07 14:11:55 main.go:34: TryRTMP start creating pipeline
2025/06/07 14:11:55 main.go:86: TryRTMP end of ElementFactoryMake
2025/06/07 14:11:55 main.go:126: TryRTMP using pipeline.Add
0:00:00.017550848 240193     0x358b1b00 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'capsfilter2x' is not unique in bin, not adding
2025/06/07 14:11:55 main.go:152: TryRTMP failed to add capsfilter2x
0:00:00.017592674 240193     0x358b1b00 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'mux' is not unique in bin, not adding
2025/06/07 14:11:55 main.go:161: TryRTMP failed to add flvmux
2025/06/07 14:11:55 main.go:206: TryRTMP fatal: gst.LinkMany returned false
exit status 1

2. using pipeline.AddMany

$ go run .
2025/06/07 14:13:03 main.go:34: TryRTMP start creating pipeline
2025/06/07 14:13:03 main.go:86: TryRTMP end of ElementFactoryMake
2025/06/07 14:13:03 main.go:165: TryRTMP using pipeline.AddMany
0:00:00.013906352 240660      0xde0fb00 WARN                     bin gstbin.c:1381:gst_bin_add_func:<pipeline0> Name 'mux' is not unique in bin, not adding
2025/06/07 14:13:03 main.go:169: TryRTMP fatal: pipeline.AddMany returned false
exit status 1

Notes

gst.LinkMany has been edited to how I interpret @RSWilli’s suggestion.
Line 210 exits, because I’m using a dummy destination url.

I missed something before: you are adding flvmux multiple times to the pipeline. The gst-launch command is referencing the flvmux with the name “mux” you gave, but it it only present once in the pipeline. Add and AddMany will fail if you add the element multiple times.

Try to set the env var GST_DEBUG_DUMP_DOT_DIR=. and call your gst launch command. You will see it created some *.dot files in your current directory, and you can use graphviz to render them to SVG/PNG etc. This contains the pipeline graph.

The same graph can be output by your go app by calling DebugDumpToFile (or similar) with the env var set (!!!)

You can then compare what your go script is doing differently. I suggest that you output the graph after every action while you are still learning the general concepts.