diff --git a/examples/appsink/main.go b/examples/appsink/main.go deleted file mode 100644 index a9be100..0000000 --- a/examples/appsink/main.go +++ /dev/null @@ -1,139 +0,0 @@ -// This example shows how to use the appsink element. -package main - -import ( - "fmt" - "math" - "os" - "os/signal" - - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" - "github.com/go-gst/go-gst/gst/app" -) - -func createPipeline() (*gst.Pipeline, error) { - gst.Init(nil) - - pipeline, err := gst.NewPipeline("") - if err != nil { - return nil, err - } - - src, err := gst.NewElement("audiotestsrc") - if err != nil { - return nil, err - } - - // Should this actually be a *gst.Element that produces an Appsink interface like the - // rust examples? - sink, err := app.NewAppSink() - if err != nil { - return nil, err - } - - pipeline.AddMany(src, sink.Element) - src.Link(sink.Element) - - // Tell the appsink what format we want. It will then be the audiotestsrc's job to - // provide the format we request. - // This can be set after linking the two objects, because format negotiation between - // both elements will happen during pre-rolling of the pipeline. - sink.SetCaps(gst.NewCapsFromString( - "audio/x-raw, format=S16LE, layout=interleaved, channels=1", - )) - - // Getting data out of the appsink is done by setting callbacks on it. - // The appsink will then call those handlers, as soon as data is available. - sink.SetCallbacks(&app.SinkCallbacks{ - // Add a "new-sample" callback - NewSampleFunc: func(sink *app.Sink) gst.FlowReturn { - - // Pull the sample that triggered this callback - sample := sink.PullSample() - if sample == nil { - return gst.FlowEOS - } - - // Retrieve the buffer from the sample - buffer := sample.GetBuffer() - if buffer == nil { - return gst.FlowError - } - - // At this point, buffer is only a reference to an existing memory region somewhere. - // When we want to access its content, we have to map it while requesting the required - // mode of access (read, read/write). - // - // We also know what format to expect because we set it with the caps. So we convert - // the map directly to signed 16-bit little-endian integers. - samples := buffer.Map(gst.MapRead).AsInt16LESlice() - defer buffer.Unmap() - - // Calculate the root mean square for the buffer - // (https://en.wikipedia.org/wiki/Root_mean_square) - var square float64 - for _, i := range samples { - square += float64(i * i) - } - rms := math.Sqrt(square / float64(len(samples))) - fmt.Println("rms:", rms) - - return gst.FlowOK - }, - }) - - return pipeline, nil -} - -func handleMessage(msg *gst.Message) error { - - switch msg.Type() { - case gst.MessageEOS: - return app.ErrEOS - case gst.MessageError: - return msg.ParseError() - } - - return nil -} - -func mainLoop(pipeline *gst.Pipeline) error { - - // Start the pipeline - pipeline.SetState(gst.StatePlaying) - - // Retrieve the bus from the pipeline - bus := pipeline.GetPipelineBus() - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - <-c - pipeline.SendEvent(gst.NewEOSEvent()) - }() - - // Loop over messsages from the pipeline - for { - msg := bus.TimedPop(gst.ClockTimeNone) - if msg == nil { - break - } - if err := handleMessage(msg); err != nil { - return err - } - } - - return nil -} - -func main() { - examples.Run(func() error { - var pipeline *gst.Pipeline - var err error - if pipeline, err = createPipeline(); err != nil { - return err - } - return mainLoop(pipeline) - }) -} diff --git a/examples/appsrc/main.go b/examples/appsrc/main.go deleted file mode 100644 index 4dda389..0000000 --- a/examples/appsrc/main.go +++ /dev/null @@ -1,168 +0,0 @@ -// This example shows how to use the appsrc element. -package main - -import ( - "fmt" - "image" - "image/color" - "time" - - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" - "github.com/go-gst/go-gst/gst/app" - "github.com/go-gst/go-gst/gst/video" -) - -const width = 320 -const height = 240 - -func createPipeline() (*gst.Pipeline, error) { - gst.Init(nil) - - // Create a pipeline - pipeline, err := gst.NewPipeline("") - if err != nil { - return nil, err - } - - // Create the elements - elems, err := gst.NewElementMany("appsrc", "videoconvert", "autovideosink") - if err != nil { - return nil, err - } - - // Add the elements to the pipeline and link them - pipeline.AddMany(elems...) - gst.ElementLinkMany(elems...) - - // Get the app sourrce from the first element returned - src := app.SrcFromElement(elems[0]) - - // Specify the format we want to provide as application into the pipeline - // by creating a video info with the given format and creating caps from it for the appsrc element. - videoInfo := video.NewInfo(). - WithFormat(video.FormatRGBA, width, height). - WithFPS(gst.Fraction(2, 1)) - - src.SetCaps(videoInfo.ToCaps()) - src.SetProperty("format", gst.FormatTime) - - // Initialize a frame counter - var i int - - // Get all 256 colors in the RGB8P palette. - palette := video.FormatRGB8P.Palette() - - // Since our appsrc element operates in pull mode (it asks us to provide data), - // we add a handler for the need-data callback and provide new data from there. - // In our case, we told gstreamer that we do 2 frames per second. While the - // buffers of all elements of the pipeline are still empty, this will be called - // a couple of times until all of them are filled. After this initial period, - // this handler will be called (on average) twice per second. - src.SetCallbacks(&app.SourceCallbacks{ - NeedDataFunc: func(self *app.Source, _ uint) { - - // If we've reached the end of the palette, end the stream. - if i == len(palette) { - src.EndStream() - return - } - - fmt.Println("Producing frame:", i) - - // Create a buffer that can hold exactly one video RGBA frame. - buffer := gst.NewBufferWithSize(videoInfo.Size()) - - // For each frame we produce, we set the timestamp when it should be displayed - // The autovideosink will use this information to display the frame at the right time. - buffer.SetPresentationTimestamp(gst.ClockTime(time.Duration(i) * 500 * time.Millisecond)) - - // Produce an image frame for this iteration. - pixels := produceImageFrame(palette[i]) - - // At this point, buffer is only a reference to an existing memory region somewhere. - // When we want to access its content, we have to map it while requesting the required - // mode of access (read, read/write). - // See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html - // - // There are convenience wrappers for building buffers directly from byte sequences as - // well. - buffer.Map(gst.MapWrite).WriteData(pixels) - buffer.Unmap() - - // Push the buffer onto the pipeline. - self.PushBuffer(buffer) - - i++ - }, - }) - - return pipeline, nil -} - -func produceImageFrame(c color.Color) []uint8 { - upLeft := image.Point{0, 0} - lowRight := image.Point{width, height} - img := image.NewRGBA(image.Rectangle{upLeft, lowRight}) - - for x := 0; x < width; x++ { - for y := 0; y < height; y++ { - img.Set(x, y, c) - } - } - - return img.Pix -} - -func handleMessage(msg *gst.Message) error { - switch msg.Type() { - case gst.MessageEOS: - return app.ErrEOS - case gst.MessageError: - gerr := msg.ParseError() - if debug := gerr.DebugString(); debug != "" { - fmt.Println(debug) - } - return gerr - } - return nil -} - -func mainLoop(loop *glib.MainLoop, pipeline *gst.Pipeline) error { - // Start the pipeline - - // Due to recent changes in the bindings - the finalizers might fire on the pipeline - // prematurely when it's passed between scopes. So when you do this, it is safer to - // take a reference that you dispose of when you are done. There is an alternative - // to this method in other examples. - pipeline.Ref() - defer pipeline.Unref() - - pipeline.SetState(gst.StatePlaying) - - // Retrieve the bus from the pipeline and add a watch function - pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool { - if err := handleMessage(msg); err != nil { - fmt.Println(err) - loop.Quit() - return false - } - return true - }) - - loop.Run() - - return nil -} - -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - var pipeline *gst.Pipeline - var err error - if pipeline, err = createPipeline(); err != nil { - return err - } - return mainLoop(loop, pipeline) - }) -} diff --git a/examples/bins/main.go b/examples/bins/main.go index a4d279a..d318a76 100644 --- a/examples/bins/main.go +++ b/examples/bins/main.go @@ -2,35 +2,35 @@ package main import ( "fmt" - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" - "os" + "time" + + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/go-gst/go-gst/pkg/gst" ) -func runPipeline(mainLoop *glib.MainLoop) error { - gst.Init(&os.Args) +func main() { + gst.Init() - bin, err := gst.NewBinFromString("fakesrc num-buffers=5 ! fakesink", true) + mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false) + + bin, err := gst.ParseBinFromDescription("fakesrc num-buffers=5 ! fakesink", true) if err != nil { - return err + panic(err) } - pipeline, err := gst.NewPipeline("pipeline") - if err != nil { - return err - } + pipeline := gst.NewPipeline("pipeline") - pipeline.Add(bin.Element) - pipeline.GetBus().AddWatch(func(msg *gst.Message) bool { + pipeline.Add(bin) + + pipeline.Bus().AddWatch(0, func(bus *gst.Bus, msg *gst.Message) bool { switch msg.Type() { - case gst.MessageEOS: // When end-of-stream is received stop the main loop - bin.BlockSetState(gst.StateNull) + case gst.MessageEos: // When end-of-stream is received stop the main loop + bin.BlockSetState(gst.StateNull, gst.ClockTime(time.Second)) mainLoop.Quit() case gst.MessageError: // Error messages are always fatal - err := msg.ParseError() + err, debug := msg.ParseError() fmt.Println("ERROR:", err.Error()) - if debug := err.DebugString(); debug != "" { + if debug != "" { fmt.Println("DEBUG:", debug) } mainLoop.Quit() @@ -46,11 +46,5 @@ func runPipeline(mainLoop *glib.MainLoop) error { pipeline.SetState(gst.StatePlaying) // Block on the main loop - return mainLoop.RunError() -} - -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - return runPipeline(loop) - }) + mainLoop.Run() } diff --git a/examples/common.go b/examples/common.go deleted file mode 100644 index f084917..0000000 --- a/examples/common.go +++ /dev/null @@ -1,31 +0,0 @@ -package examples - -import ( - "fmt" - - "github.com/go-gst/go-glib/glib" -) - -// Run is used to wrap the given function in a main loop and print any error -func Run(f func() error) { - mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false) - - go func() { - if err := f(); err != nil { - fmt.Println("ERROR!", err) - } - mainLoop.Quit() - }() - - mainLoop.Run() -} - -// RunLoop is used to wrap the given function in a main loop and print any error. -// The main loop itself is passed to the function for more control over exiting. -func RunLoop(f func(*glib.MainLoop) error) { - mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false) - - if err := f(mainLoop); err != nil { - fmt.Println("ERROR!", err) - } -} diff --git a/examples/custom_events/main.go b/examples/custom_events/main.go index b158668..d36a938 100644 --- a/examples/custom_events/main.go +++ b/examples/custom_events/main.go @@ -2,13 +2,12 @@ package main import ( - "errors" "fmt" + "runtime" "time" - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/go-gst/go-gst/pkg/gst" ) // ExampleCustomEvent demonstrates a custom event structue. Currerntly nested structs @@ -19,10 +18,10 @@ type ExampleCustomEvent struct { } func createPipeline() (*gst.Pipeline, error) { - gst.Init(nil) + gst.Init() // Create a new pipeline from a launch string - pipeline, err := gst.NewPipelineFromString( + ret, err := gst.ParseLaunch( "audiotestsrc name=src ! queue max-size-time=2000000000 ! fakesink name=sink sync=true", ) @@ -30,31 +29,35 @@ func createPipeline() (*gst.Pipeline, error) { return nil, err } - // Retrieve the sink element - sinks, err := pipeline.GetSinkElements() - if err != nil { - return nil, err - } else if len(sinks) != 1 { - return nil, errors.New("expected one sink back") - } - sink := sinks[0] + pipeline := ret.(*gst.Pipeline) - // Get the sink pad - sinkpad := sink.GetStaticPad("sink") + var sink *gst.Element + var sinkpad *gst.Pad + + // Retrieve the sink pad + for v := range pipeline.IterateSinks().Values() { + sink = v.(*gst.Element) + sinkpad = sink.StaticPad("sink") + break + } + + if sink == nil || sinkpad == nil { + return nil, fmt.Errorf("could not find sink") + } // Add a probe for out custom event sinkpad.AddProbe(gst.PadProbeTypeEventDownstream, func(self *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn { // Retrieve the event from the probe - ev := info.GetEvent() + ev := info.Event() // Extra check to make sure it is the right type. - if ev.Type() != gst.EventTypeCustomDownstream { + if ev.Type() != gst.EventCustomDownstream { return gst.PadProbeHandled } // Unmarshal the event into our custom one var customEvent ExampleCustomEvent - if err := ev.GetStructure().UnmarshalInto(&customEvent); err != nil { + if err := ev.Structure().UnmarshalInto(&customEvent); err != nil { fmt.Println("Could not parse the custom event!") return gst.PadProbeHandled } @@ -66,9 +69,9 @@ func createPipeline() (*gst.Pipeline, error) { // This is becaues the SendEvent method blocks and this could cause a dead lock sending the // event directly from the probe. This is the near equivalent of using go func() { ... }(), // however displayed this way for demonstration purposes. - sink.CallAsync(func() { + sink.CallAsync(func(el gst.Elementer) { fmt.Println("Send EOS is true, sending eos") - if !pipeline.SendEvent(gst.NewEOSEvent()) { + if !pipeline.SendEvent(gst.NewEventEos()) { fmt.Println("WARNING: Failed to send EOS to pipeline") } fmt.Println("Sent EOS") @@ -82,11 +85,11 @@ func createPipeline() (*gst.Pipeline, error) { return pipeline, nil } -func mainLoop(loop *glib.MainLoop, pipeline *gst.Pipeline) error { +func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) { // Create a watch on the pipeline to kill the main loop when EOS is received - pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool { + pipeline.Bus().AddWatch(0, func(bus *gst.Bus, msg *gst.Message) bool { switch msg.Type() { - case gst.MessageEOS: + case gst.MessageEos: fmt.Println("Got EOS message") loop.Quit() default: @@ -108,7 +111,8 @@ func mainLoop(loop *glib.MainLoop, pipeline *gst.Pipeline) error { ev.SendEOS = true } st := gst.MarshalStructure(ev) - if !pipeline.SendEvent(gst.NewCustomEvent(gst.EventTypeCustomDownstream, st)) { + + if !pipeline.SendEvent(gst.NewEventCustom(gst.EventCustomDownstream, st)) { fmt.Println("Warning: failed to send custom event") } if count == 3 { @@ -124,18 +128,20 @@ func mainLoop(loop *glib.MainLoop, pipeline *gst.Pipeline) error { // done with the new scope. An alternative is to declare Keep() *after* where you know // you will be done with the object. This instructs the runtime to defer the finalizer // until after this point is passed in the code execution. - pipeline.Keep() - return loop.RunError() + loop.Run() + + runtime.KeepAlive(pipeline) } func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - var pipeline *gst.Pipeline - var err error - if pipeline, err = createPipeline(); err != nil { - return err - } - return mainLoop(loop, pipeline) - }) + pipeline, err := createPipeline() + + if err != nil { + panic(err) + } + + mainloop := glib.NewMainLoop(glib.MainContextDefault(), false) + + runPipeline(mainloop, pipeline) } diff --git a/examples/decodebin/main.go b/examples/decodebin/main.go index 7319211..f29d113 100644 --- a/examples/decodebin/main.go +++ b/examples/decodebin/main.go @@ -37,48 +37,48 @@ import ( "fmt" "os" "strings" + "weak" - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/go-gst/go-gst/pkg/gst" ) var srcFile string func buildPipeline() (*gst.Pipeline, error) { - gst.Init(nil) + gst.Init() - pipeline, err := gst.NewPipeline("") - if err != nil { - return nil, err + pipeline := gst.NewPipeline("") + + src, ok := gst.ElementFactoryMake("filesrc", "").(*gst.Element) + + if !ok { + return nil, fmt.Errorf("could not create filesource") } - src, err := gst.NewElement("filesrc") - if err != nil { - return nil, err + decodebin, ok := gst.ElementFactoryMake("decodebin", "").(*gst.Bin) + if !ok { + return nil, fmt.Errorf("could not create decodebin") } - decodebin, err := gst.NewElement("decodebin") - if err != nil { - return nil, err - } - - src.Set("location", srcFile) + src.SetObjectProperty("location", srcFile) pipeline.AddMany(src, decodebin) src.Link(decodebin) + // prevent reference cycles with the connect handler: + weakDecodeBin := weak.Make(decodebin) + // Connect to decodebin's pad-added signal, that is emitted whenever // it found another stream from the input file and found a way to decode it to its raw format. // decodebin automatically adds a src-pad for this raw stream, which // we can use to build the follow-up pipeline. - decodebin.Connect("pad-added", func(self *gst.Element, srcPad *gst.Pad) { - + decodebin.ConnectPadAdded(func(srcPad *gst.Pad) { // Try to detect whether this is video or audio var isAudio, isVideo bool - caps := srcPad.GetCurrentCaps() - for i := 0; i < caps.GetSize(); i++ { - st := caps.GetStructureAt(i) + caps := srcPad.CurrentCaps() + for i := 0; i < int(caps.Size()); i++ { + st := caps.Structure(uint(i)) if strings.HasPrefix(st.Name(), "audio/") { isAudio = true } @@ -93,84 +93,74 @@ func buildPipeline() (*gst.Pipeline, error) { err := errors.New("could not detect media stream type") // We can send errors directly to the pipeline bus if they occur. // These will be handled downstream. - msg := gst.NewErrorMessage(self, gst.NewGError(1, err), fmt.Sprintf("Received caps: %s", caps.String()), nil) - pipeline.GetPipelineBus().Post(msg) + msg := gst.NewMessageError(weakDecodeBin.Value(), err, fmt.Sprintf("Received caps: %s", caps.String())) + pipeline.Bus().Post(msg) return } if isAudio { // decodebin found a raw audiostream, so we build the follow-up pipeline to // play it on the default audio playback device (using autoaudiosink). - elements, err := gst.NewElementMany("queue", "audioconvert", "audioresample", "autoaudiosink") + audiosink, err := gst.ParseBinFromDescription("queue ! audioconvert ! audioresample ! autoaudiosink", true) if err != nil { - // We can create custom errors (with optional structures) and send them to the pipeline bus. - // The first argument reflects the source of the error, the second is the error itself, followed by a debug string. - msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "Could not create elements for audio pipeline", nil) - pipeline.GetPipelineBus().Post(msg) + msg := gst.NewMessageError(weakDecodeBin.Value(), err, "Could not create elements for audio pipeline") + pipeline.Bus().Post(msg) return } - pipeline.AddMany(elements...) - gst.ElementLinkMany(elements...) + pipeline.Add(audiosink) // !!ATTENTION!!: // This is quite important and people forget it often. Without making sure that // the new elements have the same state as the pipeline, things will fail later. // They would still be in Null state and can't process data. - for _, e := range elements { - e.SyncStateWithParent() - } - // The queue was the first element returned above - queue := elements[0] + audiosink.SyncStateWithParent() + // Get the queue element's sink pad and link the decodebin's newly created // src pad for the audio stream to it. - sinkPad := queue.GetStaticPad("sink") + sinkPad := audiosink.StaticPad("sink") srcPad.Link(sinkPad) } else if isVideo { // decodebin found a raw videostream, so we build the follow-up pipeline to // display it using the autovideosink. - elements, err := gst.NewElementMany("queue", "videoconvert", "videoscale", "autovideosink") + videosink, err := gst.ParseBinFromDescription("queue ! videoconvert ! videoscale ! autovideosink", true) if err != nil { - msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "Could not create elements for video pipeline", nil) - pipeline.GetPipelineBus().Post(msg) + msg := gst.NewMessageError(weakDecodeBin.Value(), err, "Could not create elements for video pipeline") + pipeline.Bus().Post(msg) return } - pipeline.AddMany(elements...) - gst.ElementLinkMany(elements...) + pipeline.Add(videosink) - for _, e := range elements { - e.SyncStateWithParent() - } + videosink.SyncStateWithParent() - queue := elements[0] // Get the queue element's sink pad and link the decodebin's newly created // src pad for the video stream to it. - sinkPad := queue.GetStaticPad("sink") + sinkPad := videosink.StaticPad("sink") srcPad.Link(sinkPad) } }) return pipeline, nil } -func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) error { +func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) { // Start the pipeline pipeline.SetState(gst.StatePlaying) // Add a message watch to the bus to quit on any error - pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool { + pipeline.Bus().AddWatch(0, func(bus *gst.Bus, msg *gst.Message) bool { var err error // If the stream has ended or any element posts an error to the // bus, populate error. switch msg.Type() { - case gst.MessageEOS: + case gst.MessageEos: err = errors.New("end-of-stream") case gst.MessageError: // The parsed error implements the error interface, but also // contains additional debug information. - gerr := msg.ParseError() - fmt.Println("go-gst-debug:", gerr.DebugString()) + gerr, debug := msg.ParseError() + fmt.Println("go-gst-debug:", debug) err = gerr } @@ -185,7 +175,7 @@ func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) error { }) // Block on the main loop - return loop.RunError() + loop.Run() } func main() { @@ -195,11 +185,14 @@ func main() { flag.Usage() os.Exit(1) } - examples.RunLoop(func(loop *glib.MainLoop) error { - pipeline, err := buildPipeline() - if err != nil { - return err - } - return runPipeline(loop, pipeline) - }) + + pipeline, err := buildPipeline() + + if err != nil { + panic(err) + } + + mainloop := glib.NewMainLoop(glib.MainContextDefault(), false) + + runPipeline(mainloop, pipeline) } diff --git a/examples/device_monitor/main.go b/examples/device_monitor/main.go deleted file mode 100644 index d02d8ac..0000000 --- a/examples/device_monitor/main.go +++ /dev/null @@ -1,75 +0,0 @@ -// This example uses gstreamer's device monitor api. -// -// https://gstreamer.freedesktop.org/documentation/gstreamer/gstdevicemonitor.html -package main - -import ( - "fmt" - - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" -) - -func runPipeline(loop *glib.MainLoop) error { - - gst.Init(nil) - fmt.Println("Running device monitor") - // if len(os.Args) < 2 { - // fmt.Printf("USAGE: %s \n", os.Args[0]) - // os.Exit(1) - // } - - // uri := os.Args[1] - fmt.Println("Creating device monitor") - - monitor := gst.NewDeviceMonitor() - fmt.Println("Created device monitor", monitor) - - // if err != nil { - // fmt.Println("ERROR:", err) - // os.Exit(2) - // } - caps := gst.NewCapsFromString("video/x-raw") - - monitor.AddFilter("Video/Source", caps) - - fmt.Println("Getting device monitor bus") - bus := monitor.GetBus() - fmt.Println("Got device monitor bus", bus) - - bus.AddWatch(func(msg *gst.Message) bool { - switch msg.Type() { - case gst.MessageDeviceAdded: - message := msg.ParseDeviceAdded().GetDisplayName() - fmt.Println("Added: ", message) - case gst.MessageDeviceRemoved: - message := msg.ParseDeviceRemoved().GetDisplayName() - fmt.Println("Removed: ", message) - default: - // All messages implement a Stringer. However, this is - // typically an expensive thing to do and should be avoided. - fmt.Println("Type: ", msg.Type()) - fmt.Println("Message: ", msg) - } - return true - }) - - fmt.Println("Starting device monitor") - monitor.Start() - fmt.Println("Started device monitor") - devices := monitor.GetDevices() - for i, v := range devices { - fmt.Printf("Device: %d %s\n", i, v.GetDisplayName()) - } - - loop.Run() - - return nil -} - -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - return runPipeline(loop) - }) -} diff --git a/examples/device_provider/main.go b/examples/device_provider/main.go deleted file mode 100644 index 911bf43..0000000 --- a/examples/device_provider/main.go +++ /dev/null @@ -1,78 +0,0 @@ -// This example uses gstreamer's device provider api. -// -// https://gstreamer.freedesktop.org/documentation/gstreamer/gstdeviceprovider.html -package main - -import ( - "fmt" - "os" - - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" -) - -func runPipeline(loop *glib.MainLoop) error { - - gst.Init(nil) - fmt.Println("Running device provider") - // if len(os.Args) < 2 { - // fmt.Printf("USAGE: %s \n", os.Args[0]) - // os.Exit(1) - // } - - // uri := os.Args[1] - fmt.Println("Creating device monitor") - - // provider := gst.FindDeviceProviderByName("foo") - // fmt.Println("Created device provider", provider) - - provider := gst.FindDeviceProviderByName("avfdeviceprovider") - fmt.Println("Created device provider", provider) - - if provider == nil { - fmt.Println("No provider found") - os.Exit(2) - } - - fmt.Println("Getting device provider bus") - bus := provider.GetBus() - fmt.Println("Got device provider bus", bus) - - bus.AddWatch(func(msg *gst.Message) bool { - switch msg.Type() { - case gst.MessageDeviceAdded: - message := msg.ParseDeviceAdded().GetDisplayName() - fmt.Println("Added: ", message) - case gst.MessageDeviceRemoved: - message := msg.ParseDeviceRemoved().GetDisplayName() - fmt.Println("Removed: ", message) - default: - // All messages implement a Stringer. However, this is - // typically an expensive thing to do and should be avoided. - fmt.Println("Type: ", msg.Type()) - fmt.Println("Message: ", msg) - } - return true - }) - - fmt.Println("Starting device monitor") - provider.Start() - fmt.Println("Started device monitor") - - fmt.Println("listing devices from provider") - devices := provider.GetDevices() - for i, v := range devices { - fmt.Printf("Device: %d %s\n", i, v.GetDisplayName()) - } - - loop.Run() - - return nil -} - -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - return runPipeline(loop) - }) -} diff --git a/examples/events/main.go b/examples/events/main.go index 9b07804..cd8c742 100644 --- a/examples/events/main.go +++ b/examples/events/main.go @@ -24,22 +24,26 @@ import ( "fmt" "time" - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/go-gst/go-gst/pkg/gst" ) -func runPipeline(loop *glib.MainLoop) error { - gst.Init(nil) +func main() { + gst.Init() + + mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false) // Build a pipeline with fake audio data going to a fakesink - pipeline, err := gst.NewPipelineFromString("audiotestsrc ! fakesink") + res, err := gst.ParseLaunch("audiotestsrc ! fakesink") if err != nil { - return err + fmt.Println(err) + return } + pipeline := res.(*gst.Pipeline) + // Retrieve the message bus for the pipeline - bus := pipeline.GetPipelineBus() + bus := pipeline.Bus() // Start the pipeline pipeline.SetState(gst.StatePlaying) @@ -47,24 +51,22 @@ func runPipeline(loop *glib.MainLoop) error { // This sets the bus's signal handler (don't be mislead by the "add", there can only be one). // Every message from the bus is passed through this function. Its return value determines // whether the handler wants to be called again. - bus.AddWatch(func(msg *gst.Message) (cont bool) { - // Assume we are continuing - cont = true + bus.AddWatch(0, func(_ *gst.Bus, msg *gst.Message) bool { switch msg.Type() { - case gst.MessageEOS: + case gst.MessageEos: fmt.Println("Received EOS") // An EndOfStream event was sent to the pipeline, so we tell our main loop // to stop execution here. - loop.Quit() + mainLoop.Quit() case gst.MessageError: - err := msg.ParseError() + err, debug := msg.ParseError() fmt.Println("ERROR:", err) - fmt.Println("DEBUG:", err.DebugString()) - loop.Quit() + fmt.Println("DEBUG:", debug) + mainLoop.Quit() } - return + return true }) // Kick off a goroutine that after 5 seconds will send an eos event to the pipeline. @@ -82,31 +84,12 @@ func runPipeline(loop *glib.MainLoop) error { // Once all sinks are done handling the EOS event (and all buffers that were before the // EOS event in the pipeline already), the pipeline would post an EOS message on the bus, // essentially telling the application that the pipeline is completely drained. - pipeline.SendEvent(gst.NewEOSEvent()) + pipeline.SendEvent(gst.NewEventEos()) return } }() - // Operate GStreamer's bus, facilliating GLib's mainloop here. - // This function call will block until you tell the mainloop to quit - // (see above for how to do this). - loop.Run() + mainLoop.Run() - // Stop the pipeline - if err := pipeline.SetState(gst.StateNull); err != nil { - fmt.Println("Error stopping pipeline:", err) - } - - // Remove the watch function from the bus. - // Again: There can always only be one watch function. - // Thus we don't have to tell it which function to remove. - bus.RemoveWatch() - - return nil -} - -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - return runPipeline(loop) - }) + return } diff --git a/examples/feature-rank/main.go b/examples/feature-rank/main.go index 6b96970..c236c6c 100644 --- a/examples/feature-rank/main.go +++ b/examples/feature-rank/main.go @@ -4,44 +4,28 @@ package main import ( "fmt" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" + "github.com/go-gst/go-gst/pkg/gst" ) -func start() error { - gst.Init(nil) - - registry := gst.GetRegistry() - - higherThanHighRank := (gst.Rank)(258) - - codec, codecErr := registry.LookupFeature("vtdec_hw") - - if codecErr == nil { - codec.SetPluginRank(higherThanHighRank) - rank := codec.GetPluginRank() - fmt.Println("vtdec_hw rank is:", rank) - } - - codec, codecErr = registry.LookupFeature("vtdec_hw") - - if codecErr == nil { - codec.SetPluginRank(gst.RankPrimary) - rank := codec.GetPluginRank() - fmt.Println("vtdec_hw rank is now:", rank) - } - - //add a feature you expect to be available to you here and change it's rank - - return codecErr -} - func main() { - examples.Run(func() error { - var err error - if err = start(); err != nil { - return err - } - return nil - }) + gst.Init() + + registry := gst.RegistryGet() + + higherThanHighRank := uint(258) + + pluginf := registry.LookupFeature("vtdec_hw") + + if pluginf == nil { + fmt.Printf("codec vtdec_hw not found") + + return + } + + plugin := gst.BasePluginFeature(pluginf) + + plugin.SetRank(higherThanHighRank) + + rank := plugin.Rank() + fmt.Println("vtdec_hw rank is:", rank) } diff --git a/examples/gif-encoder/main.go b/examples/gif-encoder/main.go deleted file mode 100644 index 579751b..0000000 --- a/examples/gif-encoder/main.go +++ /dev/null @@ -1,262 +0,0 @@ -// This example demonstrates using gstreamer to convert a video stream into image frames -// and then encoding those frames to a gif. -package main - -import ( - "flag" - "fmt" - "image" - "image/gif" - "image/jpeg" - "os" - "path" - "strings" - "time" - - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" - "github.com/go-gst/go-gst/gst/app" - "github.com/go-gst/go-gst/gst/video" -) - -var srcFile string -var outFile string - -const width = 320 -const height = 240 - -func encodeGif(mainLoop *glib.MainLoop) error { - gst.Init(nil) - - // Initialize an empty buffer for the encoded gif images. - outGif := &gif.GIF{ - Image: make([]*image.Paletted, 0), - Delay: make([]int, 0), - } - - // Create a new pipeline instance - pipeline, err := gst.NewPipeline("") - if err != nil { - return err - } - - // Create a filesrc and a decodebin element for the pipeline. - elements, err := gst.NewElementMany("filesrc", "decodebin") - if err != nil { - return nil - } - - filesrc := elements[0] // The filsrc is the first element returned. - decodebin := elements[1] // The decodebin is the second element returned. - - // Add the elements to the pipeline. - pipeline.AddMany(elements...) - - // Set the location of the source file the filesrc element and link it to the - // decodebin. - filesrc.Set("location", srcFile) - gst.ElementLinkMany(filesrc, decodebin) - - // Conncet to decodebin's pad-added signal to build the rest of the pipeline - // dynamically. For more information on why this is needed, see the decodebin - // example. - decodebin.Connect("pad-added", func(self *gst.Element, srcPad *gst.Pad) { - // Build out the rest of the elements for the pipeline pipeline. - elements, err := gst.NewElementMany("queue", "videoconvert", "videoscale", "videorate", "jpegenc") - if err != nil { - // The Bus PostError method is a convenience wrapper for building rich messages and sending them - // down the pipeline. The below call will create a new error message, populate the debug info - // with a stack trace from this goroutine, and add additional details from the provided error. - self.ErrorMessage(gst.DomainLibrary, gst.LibraryErrorFailed, "Failed to build elements for the linked pipeline", err.Error()) - return - } - - // Add the elements to the pipeline and sync their state with the pipeline - pipeline.AddMany(elements...) - for _, e := range elements { - e.SyncStateWithParent() - } - - // Retrieve direct references to the elements for clarity. - queue := elements[0] - videoconvert := elements[1] - videoscale := elements[2] - videorate := elements[3] - jpegenc := elements[4] - - // Start linking elements - - queue.Link(videoconvert) - - // We need to tell the pipeline the output format we want. Here we are going to request - // RGBx color with predefined boundaries and 5 frames per second. - videoInfo := video.NewInfo(). - WithFormat(video.FormatRGBx, width, height). - WithFPS(gst.Fraction(5, 1)) - - // videoconvert.LinkFiltered(videoscale, videoInfo.ToCaps()) - gst.ElementLinkMany(videoconvert, videoscale, videorate) - - videorate.LinkFiltered(jpegenc, videoInfo.ToCaps()) - - // Create an app sink that we are going to use to pull images from the pipeline - // one at a time. (An error can happen here too, but for the sake of brevity...) - appSink, _ := app.NewAppSink() - pipeline.Add(appSink.Element) - jpegenc.Link(appSink.Element) - appSink.SyncStateWithParent() - appSink.SetWaitOnEOS(false) - - // We can query the decodebin for the duration of the video it received. We can then - // use this value to calculate the total number of frames we expect to produce. - query := gst.NewDurationQuery(gst.FormatTime) - if ok := self.Query(query); !ok { - self.ErrorMessage(gst.DomainLibrary, gst.LibraryErrorFailed, "Failed to query video duration from decodebin", err.Error()) - return - } - - // Fetch the result from the query. - _, duration := query.ParseDuration() - - // This value is in nanoseconds. Since we told the videorate element to produce 5 frames - // per second, we know the total frames will be (duration / 1e+9) * 5. - totalFrames := int((time.Duration(duration) * time.Nanosecond).Seconds()) * 5 - - // Getting data out of the sink is done by setting callbacks. Each new sample - // will be a new jpeg image from the pipeline. - var frameNum int - appSink.SetCallbacks(&app.SinkCallbacks{ - // We need to define an EOS callback on the sink for when we receive an EOS - // upstream. This gives us an opportunity to cleanup and then signal the pipeline - // that we are ready to be shut down. - EOSFunc: func(sink *app.Sink) { - fmt.Println("\nWriting the results of the gif to", outFile) - file, err := os.Create(outFile) - if err != nil { - fmt.Println("Could not create output file:", err) - return - } - defer file.Close() - if err := gif.EncodeAll(file, outGif); err != nil { - fmt.Println("Could not encode images to gif format!", err) - } - // Signal the pipeline that we've completed EOS. - // (this should not be required, need to investigate) - pipeline.GetPipelineBus().Post(gst.NewEOSMessage(appSink)) - }, - NewSampleFunc: func(sink *app.Sink) gst.FlowReturn { - // Increment the frame number counter - frameNum++ - - if frameNum > totalFrames { - // If we've reached the total number of frames we are expecting. We can - // signal the main loop to quit. - // This needs to be done from a goroutine to not block the app sink - // callback. - return gst.FlowEOS - } - - // Pull the sample from the sink - sample := sink.PullSample() - if sample == nil { - return gst.FlowOK - } - defer sample.Unref() - - fmt.Printf("\033[2K\r") - fmt.Printf("Processing image frame %d/%d", frameNum, totalFrames) - - // Retrieve the buffer from the sample. - buffer := sample.GetBuffer() - - // We can get an io.Reader directly from the buffer. - img, err := jpeg.Decode(buffer.Reader()) - if err != nil { - self.ErrorMessage(gst.DomainLibrary, gst.LibraryErrorFailed, "Error decoding jpeg frame", err.Error()) - return gst.FlowError - } - - // Create a new paletted image with the same bounds as the pulled one - frame := image.NewPaletted(img.Bounds(), video.FormatRGB8P.Palette()) - - // Iterate the bounds of the image and set the pixels in their correct place. - for x := 1; x <= img.Bounds().Dx(); x++ { - for y := 1; y <= img.Bounds().Dy(); y++ { - frame.Set(x, y, img.At(x, y)) - } - } - - // Append the image data to the gif - outGif.Image = append(outGif.Image, frame) - outGif.Delay = append(outGif.Delay, 0) - return gst.FlowOK - }, - }) - - // Link the src pad to the queue - srcPad.Link(queue.GetStaticPad("sink")) - }) - - fmt.Println("Encoding video to gif") - - // Now that the pipeline is all set up we can start it. - pipeline.SetState(gst.StatePlaying) - - // Add a watch on the bus on the pipeline and catch any errors - // that happen. - var pipelineErr error - pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool { - switch msg.Type() { - case gst.MessageEOS: - mainLoop.Quit() - case gst.MessageError: - gerr := msg.ParseError() - fmt.Println("ERROR:", gerr.Error()) - if debug := gerr.DebugString(); debug != "" { - fmt.Println("DEBUG") - fmt.Println(debug) - } - mainLoop.Quit() - pipelineErr = gerr - return false - } - - return true - }) - - // Iterate on the main loop until the pipeline is finished. - mainLoop.Run() - - return pipelineErr -} - -func main() { - // Add flag arguments - flag.StringVar(&srcFile, "i", "", "The video to encode to gif. This argument is required.") - flag.StringVar(&outFile, "o", "", "The file to output the gif to. By default a file is created in this directory with the same name as the input.") - - // Parse the command line - flag.Parse() - - // Make sure the user provided a source file - if srcFile == "" { - flag.Usage() - fmt.Println("The input file cannot be empty!") - os.Exit(1) - } - - // If the user did not provide a destination file, generate one. - if outFile == "" { - base := path.Base(srcFile) - spl := strings.Split(base, ".") - if len(spl) < 3 { - outFile = spl[0] - } else { - outFile = strings.Join(spl[:len(spl)-2], ".") - } - outFile = outFile + ".gif" - } - - examples.RunLoop(encodeGif) -} diff --git a/examples/launch/main.go b/examples/launch/main.go index 1a2984b..d9dc90a 100644 --- a/examples/launch/main.go +++ b/examples/launch/main.go @@ -4,46 +4,89 @@ package main import ( - "errors" "fmt" "os" "strings" + "time" - "github.com/go-gst/go-glib/glib" - "github.com/go-gst/go-gst/examples" - "github.com/go-gst/go-gst/gst" + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/go-gst/go-gst/pkg/gst" ) -func runPipeline(mainLoop *glib.MainLoop) error { - if len(os.Args) == 1 { - return errors.New("pipeline string cannot be empty") - } +func main() { + gst.Init() - gst.Init(&os.Args) + mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false) // Let GStreamer create a pipeline from the parsed launch syntax on the cli. - pipeline, err := gst.NewPipelineFromString(strings.Join(os.Args[1:], " ")) + res, err := gst.ParseLaunch(strings.Join(os.Args[1:], " ")) if err != nil { - return err + fmt.Printf("Parse error: %v", err) + return } + pipeline := res.(*gst.Pipeline) + // Add a message handler to the pipeline bus, printing interesting information to the console. - pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool { + pipeline.Bus().AddWatch(0, func(_ *gst.Bus, msg *gst.Message) bool { switch msg.Type() { - case gst.MessageEOS: // When end-of-stream is received stop the main loop - pipeline.BlockSetState(gst.StateNull) + case gst.MessageEos: // When end-of-stream is received stop the main loop + pipeline.BlockSetState(gst.StateNull, gst.ClockTime(time.Second)) + mainLoop.Quit() case gst.MessageError: // Error messages are always fatal - err := msg.ParseError() + err, debug := msg.ParseError() fmt.Println("ERROR:", err.Error()) - if debug := err.DebugString(); debug != "" { + if debug != "" { fmt.Println("DEBUG:", debug) } mainLoop.Quit() + + case gst.MessageAny: + case gst.MessageApplication: + case gst.MessageAsyncDone: + case gst.MessageAsyncStart: + case gst.MessageBuffering: + case gst.MessageClockLost: + case gst.MessageClockProvide: + case gst.MessageDeviceAdded: + case gst.MessageDeviceChanged: + case gst.MessageDeviceRemoved: + case gst.MessageDurationChanged: + case gst.MessageElement: + case gst.MessageExtended: + case gst.MessageHaveContext: + case gst.MessageInfo: + case gst.MessageInstantRateRequest: + case gst.MessageLatency: + case gst.MessageNeedContext: + case gst.MessageNewClock: + case gst.MessageProgress: + case gst.MessagePropertyNotify: + case gst.MessageQos: + case gst.MessageRedirect: + case gst.MessageRequestState: + case gst.MessageResetTime: + case gst.MessageSegmentDone: + case gst.MessageSegmentStart: + case gst.MessageStateChanged: + old, state, pending := msg.ParseStateChanged() + + fmt.Printf("State changed: %s => %s (%s)\n", old, state, pending) + case gst.MessageStateDirty: + case gst.MessageStepDone: + case gst.MessageStepStart: + case gst.MessageStreamCollection: + case gst.MessageStreamStart: + case gst.MessageStreamStatus: + case gst.MessageStreamsSelected: + case gst.MessageStructureChange: + case gst.MessageTag: + case gst.MessageToc: + case gst.MessageUnknown: + case gst.MessageWarning: default: - // All messages implement a Stringer. However, this is - // typically an expensive thing to do and should be avoided. - fmt.Println(msg) + panic("unexpected gst.MessageType") } return true }) @@ -52,11 +95,8 @@ func runPipeline(mainLoop *glib.MainLoop) error { pipeline.SetState(gst.StatePlaying) // Block on the main loop - return mainLoop.RunError() -} -func main() { - examples.RunLoop(func(loop *glib.MainLoop) error { - return runPipeline(loop) - }) + mainLoop.Run() + + return } diff --git a/generator.go b/generator.go index 7fa74f0..7a4d532 100644 --- a/generator.go +++ b/generator.go @@ -120,7 +120,7 @@ var Data = genmain.Overlay( MiniObjectExtenderBorrows(), }, Postprocessors: map[string][]girgen.Postprocessor{ - "Gst-1": {ElementFactoryMakeWithProperties}, + "Gst-1": {ElementFactoryMakeWithProperties, ElementBlockSetState, BinAddMany, ElementLinkMany, IteratorValues, StructureGoMarshal}, "GstMpegts-1": {GstUseUnstableAPI}, "GstWebRTC-1": {GstUseUnstableAPI, FixWebrtcPkgConfig}, }, @@ -136,6 +136,9 @@ var Data = genmain.Overlay( types.AbsoluteFilter("C.gst_mpegts_descriptor_parse_dvb_frequency_list"), types.AbsoluteFilter("C.gst_source_buffer_get_buffered"), + // Excluded because the additional_info param seems to have changed between my local gstreamer and 1.24.10 (used in github actions) + types.AbsoluteFilter("C.gst_mpegts_descriptor_parse_registration"), + // In-out array pointer, not very go like and not correctly handled by girgen, needs custom implementation types.AbsoluteFilter("C.gst_audio_get_channel_reorder_map"), @@ -252,6 +255,172 @@ func FixWebrtcPkgConfig(nsgen *girgen.NamespaceGenerator) error { return nil } +func IteratorValues(nsgen *girgen.NamespaceGenerator) error { + fg := nsgen.MakeFile("iteratorvalues.go") + + p := fg.Pen() + + fg.Header().Import("iter") + + p.Line(` + // Values allows you to access the values from the iterator in a go for loop via function iterators + func (it *Iterator) Values() iter.Seq[any] { + return func(yield func(any) bool) { + for { + v, ret := it.Next() + switch ret { + case IteratorDone: + return + case IteratorResync: + it.Resync() + case IteratorOK: + if !yield(v.GoValue()) { + return + } + + case IteratorError: + panic("iterator values failed") + default: + panic("iterator values returned unknown state") + } + } + } + } + `) + + return nil +} + +func StructureGoMarshal(nsgen *girgen.NamespaceGenerator) error { + fg := nsgen.MakeFile("structuremarshal.go") + + p := fg.Pen() + + fg.Header().NeedsExternGLib() + fg.Header().Import("reflect") + + p.Line(` + // MarshalStructure will convert the given go struct into a GstStructure. Currently nested + // structs are not supported. You can control the mapping of the field names via the tags of the go struct. + func MarshalStructure(data interface{}) *Structure { + typeOf := reflect.TypeOf(data) + valsOf := reflect.ValueOf(data) + st := NewStructureEmpty(typeOf.Name()) + for i := 0; i < valsOf.NumField(); i++ { + gval := valsOf.Field(i).Interface() + + fieldName, ok := typeOf.Field(i).Tag.Lookup("gst") + + if !ok { + fieldName = typeOf.Field(i).Name + } + + st.SetValue(fieldName, coreglib.NewValue(gval)) + } + return st + } + + // UnmarshalInto will unmarshal this structure into the given pointer. The object + // reflected by the pointer must be non-nil. You can control the mapping of the field names via the tags of the go struct. + func (s *Structure) UnmarshalInto(data interface{}) error { + rv := reflect.ValueOf(data) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("data is invalid (nil or non-pointer)") + } + + val := reflect.ValueOf(data).Elem() + nVal := rv.Elem() + for i := 0; i < val.NumField(); i++ { + nvField := nVal.Field(i) + + fieldName, ok := val.Type().Field(i).Tag.Lookup("gst") + + if !ok { + fieldName = val.Type().Field(i).Name + } + + val := s.Value(fieldName) + + nvField.Set(reflect.ValueOf(val)) + } + + return nil + } + `) + + return nil +} +func BinAddMany(nsgen *girgen.NamespaceGenerator) error { + fg := nsgen.MakeFile("binaddmany.go") + + p := fg.Pen() + + p.Line(` + // AddMany repeatedly calls Add for each param + func (bin *Bin) AddMany(elements... Elementer) bool { + for _, el := range elements { + if !bin.Add(el) { + return false + } + } + + return true + } + `) + + return nil +} + +func ElementLinkMany(nsgen *girgen.NamespaceGenerator) error { + fg := nsgen.MakeFile("elementlinkmany.go") + + p := fg.Pen() + + p.Line(` + // LinkMany links the given elements in the order passed + func LinkMany(elements... Elementer) bool { + if len(elements) == 0 { + return true + } + + current := elements[0].(*Element) + + for _, next := range elements[1:] { + if ! current.Link(next) { + return false + } + + current = next.(*Element) + } + + return true + } + `) + + return nil +} + +func ElementBlockSetState(nsgen *girgen.NamespaceGenerator) error { + fg := nsgen.MakeFile("elementblocksetstate.go") + + p := fg.Pen() + + p.Line(` + // BlockSetState is a convenience wrapper around calling SetState and State to wait for async state changes. See State for more info. + func (el *Element) BlockSetState(state State, timeout ClockTime) StateChangeReturn { + ret := el.SetState(state) + + if ret == StateChangeAsync { + _, _, ret = el.State(timeout) + } + + return ret + } + `) + + return nil +} + func ElementFactoryMakeWithProperties(nsgen *girgen.NamespaceGenerator) error { fg := nsgen.MakeFile("elementfactory.go") fg.Header().NeedsExternGLib() diff --git a/go.mod b/go.mod index 981ea82..b4e3aff 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-gst/go-gst -go 1.23.2 +go 1.24 require ( github.com/diamondburned/gotk4 v0.3.1 diff --git a/gst/go.mod b/gst/go.mod new file mode 100644 index 0000000..701e7d0 --- /dev/null +++ b/gst/go.mod @@ -0,0 +1,13 @@ +module github.com/go-gst/go-gst/gst + +go 1.23.4 + +require ( + github.com/go-gst/go-glib v1.4.1-0.20241218143927-ca07c1459298 + github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c +) + +require ( + github.com/mattn/go-pointer v0.0.1 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect +) diff --git a/gst/go.sum b/gst/go.sum new file mode 100644 index 0000000..e557ff3 --- /dev/null +++ b/gst/go.sum @@ -0,0 +1,10 @@ +github.com/go-gst/go-glib v1.4.0 h1:FB2uVfB0uqz7/M6EaDdWWlBZRQpvFAbWfL7drdw8lAE= +github.com/go-gst/go-glib v1.4.0/go.mod h1:GUIpWmkxQ1/eL+FYSjKpLDyTZx6Vgd9nNXt8dA31d5M= +github.com/go-gst/go-glib v1.4.1-0.20241218143927-ca07c1459298 h1:TFtT/wzJA0BQ2/LblfbPLl1GVzZ1h0sJ77AvGFHvZ+c= +github.com/go-gst/go-glib v1.4.1-0.20241218143927-ca07c1459298/go.mod h1:ZWT4LXOO2PH8lSNu/dR5O2yoNQJKEgmijNa2d7nByK8= +github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c h1:x8kKRVDmz5BRlolmDZGcsuZ1l+js6TRL3QWBJjGVctM= +github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c/go.mod h1:qKw5ZZ0U58W6PU/7F/Lopv+14nKYmdXlOd7VnAZ17Mk= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= diff --git a/pkg/gst/gst.go b/pkg/gst/gst.go index 60000d0..c724139 100644 --- a/pkg/gst/gst.go +++ b/pkg/gst/gst.go @@ -4,6 +4,8 @@ package gst import ( "fmt" + "iter" + "reflect" "runtime" _ "runtime/cgo" "strings" @@ -57313,6 +57315,118 @@ func ElementFactoryMakeWithProperties(factoryname string, properties map[string] return _element } +// BlockSetState is a convenience wrapper around calling SetState and State to wait for async state changes. See State for more info. +func (el *Element) BlockSetState(state State, timeout ClockTime) StateChangeReturn { + ret := el.SetState(state) + + if ret == StateChangeAsync { + _, _, ret = el.State(timeout) + } + + return ret +} + +// AddMany repeatedly calls Add for each param +func (bin *Bin) AddMany(elements ...Elementer) bool { + for _, el := range elements { + if !bin.Add(el) { + return false + } + } + + return true +} + +// LinkMany links the given elements in the order passed +func LinkMany(elements ...Elementer) bool { + if len(elements) == 0 { + return true + } + + current := elements[0].(*Element) + + for _, next := range elements[1:] { + if !current.Link(next) { + return false + } + + current = next.(*Element) + } + + return true +} + +// Values allows you to access the values from the iterator in a go for loop via function iterators +func (it *Iterator) Values() iter.Seq[any] { + return func(yield func(any) bool) { + for { + v, ret := it.Next() + switch ret { + case IteratorDone: + return + case IteratorResync: + it.Resync() + case IteratorOK: + if !yield(v.GoValue()) { + return + } + + case IteratorError: + panic("iterator values failed") + default: + panic("iterator values returned unknown state") + } + } + } +} + +// MarshalStructure will convert the given go struct into a GstStructure. Currently nested +// structs are not supported. You can control the mapping of the field names via the tags of the go struct. +func MarshalStructure(data interface{}) *Structure { + typeOf := reflect.TypeOf(data) + valsOf := reflect.ValueOf(data) + st := NewStructureEmpty(typeOf.Name()) + for i := 0; i < valsOf.NumField(); i++ { + gval := valsOf.Field(i).Interface() + + fieldName, ok := typeOf.Field(i).Tag.Lookup("gst") + + if !ok { + fieldName = typeOf.Field(i).Name + } + + st.SetValue(fieldName, coreglib.NewValue(gval)) + } + return st +} + +// UnmarshalInto will unmarshal this structure into the given pointer. The object +// reflected by the pointer must be non-nil. You can control the mapping of the field names via the tags of the go struct. +func (s *Structure) UnmarshalInto(data interface{}) error { + rv := reflect.ValueOf(data) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("data is invalid (nil or non-pointer)") + } + + val := reflect.ValueOf(data).Elem() + nVal := rv.Elem() + for i := 0; i < val.NumField(); i++ { + nvField := nVal.Field(i) + + fieldName, ok := val.Type().Field(i).Tag.Lookup("gst") + + if !ok { + fieldName = val.Type().Field(i).Name + } + + val := s.Value(fieldName) + + nvField.Set(reflect.ValueOf(val)) + } + + return nil +} + // Init binds to the gst_init() function. Argument parsing is not // supported. func Init() { diff --git a/pkg/gstmpegts/gstmpegts.go b/pkg/gstmpegts/gstmpegts.go index 20c5e04..030c280 100644 --- a/pkg/gstmpegts/gstmpegts.go +++ b/pkg/gstmpegts/gstmpegts.go @@ -5434,42 +5434,6 @@ func (descriptor *Descriptor) ParseMetadataStd(metadataInputLeakRate *uint32, me return _ok } -// ParseRegistration extracts the Registration information from descriptor. -// -// The function returns the following values: -// -// - registrationId: registration ID (in host endiannes). -// - additionalInfo (optional): additional information. -// - ok: TRUE if parsing succeeded, else FALSE. -func (descriptor *Descriptor) ParseRegistration() (uint32, []byte, bool) { - var _arg0 *C.GstMpegtsDescriptor // out - var _arg1 C.guint32 // in - var _arg2 *C.guint8 // in - var _arg3 C.gsize // in - var _cret C.gboolean // in - - _arg0 = (*C.GstMpegtsDescriptor)(gextras.StructNative(unsafe.Pointer(descriptor))) - - _cret = C.gst_mpegts_descriptor_parse_registration(_arg0, &_arg1, &_arg2, &_arg3) - runtime.KeepAlive(descriptor) - - var _registrationId uint32 // out - var _additionalInfo []byte // out - var _ok bool // out - - _registrationId = uint32(_arg1) - if _arg2 != nil { - defer C.free(unsafe.Pointer(_arg2)) - _additionalInfo = make([]byte, _arg3) - copy(_additionalInfo, unsafe.Slice((*byte)(unsafe.Pointer(_arg2)), _arg3)) - } - if _cret != 0 { - _ok = true - } - - return _registrationId, _additionalInfo, _ok -} - // ParseSatelliteDeliverySystem extracts the satellite delivery system // information from descriptor. //