mirror of
https://github.com/go-gst/go-gst.git
synced 2025-10-05 16:06:55 +08:00
port examples to new packages and add more postprocessors to reach a similar convenience feature set
This commit is contained in:
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -2,35 +2,35 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-gst/go-glib/glib"
|
"time"
|
||||||
"github.com/go-gst/go-gst/examples"
|
|
||||||
"github.com/go-gst/go-gst/gst"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"os"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPipeline(mainLoop *glib.MainLoop) error {
|
func main() {
|
||||||
gst.Init(&os.Args)
|
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 {
|
if err != nil {
|
||||||
return err
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline, err := gst.NewPipeline("pipeline")
|
pipeline := gst.NewPipeline("pipeline")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline.Add(bin.Element)
|
pipeline.Add(bin)
|
||||||
pipeline.GetBus().AddWatch(func(msg *gst.Message) bool {
|
|
||||||
|
pipeline.Bus().AddWatch(0, func(bus *gst.Bus, msg *gst.Message) bool {
|
||||||
switch msg.Type() {
|
switch msg.Type() {
|
||||||
case gst.MessageEOS: // When end-of-stream is received stop the main loop
|
case gst.MessageEos: // When end-of-stream is received stop the main loop
|
||||||
bin.BlockSetState(gst.StateNull)
|
bin.BlockSetState(gst.StateNull, gst.ClockTime(time.Second))
|
||||||
mainLoop.Quit()
|
mainLoop.Quit()
|
||||||
case gst.MessageError: // Error messages are always fatal
|
case gst.MessageError: // Error messages are always fatal
|
||||||
err := msg.ParseError()
|
err, debug := msg.ParseError()
|
||||||
fmt.Println("ERROR:", err.Error())
|
fmt.Println("ERROR:", err.Error())
|
||||||
if debug := err.DebugString(); debug != "" {
|
if debug != "" {
|
||||||
fmt.Println("DEBUG:", debug)
|
fmt.Println("DEBUG:", debug)
|
||||||
}
|
}
|
||||||
mainLoop.Quit()
|
mainLoop.Quit()
|
||||||
@@ -46,11 +46,5 @@ func runPipeline(mainLoop *glib.MainLoop) error {
|
|||||||
pipeline.SetState(gst.StatePlaying)
|
pipeline.SetState(gst.StatePlaying)
|
||||||
|
|
||||||
// Block on the main loop
|
// Block on the main loop
|
||||||
return mainLoop.RunError()
|
mainLoop.Run()
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
examples.RunLoop(func(loop *glib.MainLoop) error {
|
|
||||||
return runPipeline(loop)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,13 +2,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-gst/go-glib/glib"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"github.com/go-gst/go-gst/examples"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
"github.com/go-gst/go-gst/gst"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExampleCustomEvent demonstrates a custom event structue. Currerntly nested structs
|
// ExampleCustomEvent demonstrates a custom event structue. Currerntly nested structs
|
||||||
@@ -19,10 +18,10 @@ type ExampleCustomEvent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createPipeline() (*gst.Pipeline, error) {
|
func createPipeline() (*gst.Pipeline, error) {
|
||||||
gst.Init(nil)
|
gst.Init()
|
||||||
|
|
||||||
// Create a new pipeline from a launch string
|
// 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",
|
"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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the sink element
|
pipeline := ret.(*gst.Pipeline)
|
||||||
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]
|
|
||||||
|
|
||||||
// Get the sink pad
|
var sink *gst.Element
|
||||||
sinkpad := sink.GetStaticPad("sink")
|
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
|
// Add a probe for out custom event
|
||||||
sinkpad.AddProbe(gst.PadProbeTypeEventDownstream, func(self *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn {
|
sinkpad.AddProbe(gst.PadProbeTypeEventDownstream, func(self *gst.Pad, info *gst.PadProbeInfo) gst.PadProbeReturn {
|
||||||
// Retrieve the event from the probe
|
// Retrieve the event from the probe
|
||||||
ev := info.GetEvent()
|
ev := info.Event()
|
||||||
|
|
||||||
// Extra check to make sure it is the right type.
|
// Extra check to make sure it is the right type.
|
||||||
if ev.Type() != gst.EventTypeCustomDownstream {
|
if ev.Type() != gst.EventCustomDownstream {
|
||||||
return gst.PadProbeHandled
|
return gst.PadProbeHandled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal the event into our custom one
|
// Unmarshal the event into our custom one
|
||||||
var customEvent ExampleCustomEvent
|
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!")
|
fmt.Println("Could not parse the custom event!")
|
||||||
return gst.PadProbeHandled
|
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
|
// 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() { ... }(),
|
// event directly from the probe. This is the near equivalent of using go func() { ... }(),
|
||||||
// however displayed this way for demonstration purposes.
|
// however displayed this way for demonstration purposes.
|
||||||
sink.CallAsync(func() {
|
sink.CallAsync(func(el gst.Elementer) {
|
||||||
fmt.Println("Send EOS is true, sending eos")
|
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("WARNING: Failed to send EOS to pipeline")
|
||||||
}
|
}
|
||||||
fmt.Println("Sent EOS")
|
fmt.Println("Sent EOS")
|
||||||
@@ -82,11 +85,11 @@ func createPipeline() (*gst.Pipeline, error) {
|
|||||||
return pipeline, nil
|
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
|
// 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() {
|
switch msg.Type() {
|
||||||
case gst.MessageEOS:
|
case gst.MessageEos:
|
||||||
fmt.Println("Got EOS message")
|
fmt.Println("Got EOS message")
|
||||||
loop.Quit()
|
loop.Quit()
|
||||||
default:
|
default:
|
||||||
@@ -108,7 +111,8 @@ func mainLoop(loop *glib.MainLoop, pipeline *gst.Pipeline) error {
|
|||||||
ev.SendEOS = true
|
ev.SendEOS = true
|
||||||
}
|
}
|
||||||
st := gst.MarshalStructure(ev)
|
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")
|
fmt.Println("Warning: failed to send custom event")
|
||||||
}
|
}
|
||||||
if count == 3 {
|
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
|
// 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
|
// 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.
|
// until after this point is passed in the code execution.
|
||||||
pipeline.Keep()
|
|
||||||
|
|
||||||
return loop.RunError()
|
loop.Run()
|
||||||
|
|
||||||
|
runtime.KeepAlive(pipeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
examples.RunLoop(func(loop *glib.MainLoop) error {
|
pipeline, err := createPipeline()
|
||||||
var pipeline *gst.Pipeline
|
|
||||||
var err error
|
if err != nil {
|
||||||
if pipeline, err = createPipeline(); err != nil {
|
panic(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return mainLoop(loop, pipeline)
|
|
||||||
})
|
mainloop := glib.NewMainLoop(glib.MainContextDefault(), false)
|
||||||
|
|
||||||
|
runPipeline(mainloop, pipeline)
|
||||||
}
|
}
|
||||||
|
@@ -37,48 +37,48 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"weak"
|
||||||
|
|
||||||
"github.com/go-gst/go-glib/glib"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"github.com/go-gst/go-gst/examples"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
"github.com/go-gst/go-gst/gst"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var srcFile string
|
var srcFile string
|
||||||
|
|
||||||
func buildPipeline() (*gst.Pipeline, error) {
|
func buildPipeline() (*gst.Pipeline, error) {
|
||||||
gst.Init(nil)
|
gst.Init()
|
||||||
|
|
||||||
pipeline, err := gst.NewPipeline("")
|
pipeline := gst.NewPipeline("")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
src, ok := gst.ElementFactoryMake("filesrc", "").(*gst.Element)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("could not create filesource")
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := gst.NewElement("filesrc")
|
decodebin, ok := gst.ElementFactoryMake("decodebin", "").(*gst.Bin)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, err
|
return nil, fmt.Errorf("could not create decodebin")
|
||||||
}
|
}
|
||||||
|
|
||||||
decodebin, err := gst.NewElement("decodebin")
|
src.SetObjectProperty("location", srcFile)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
src.Set("location", srcFile)
|
|
||||||
|
|
||||||
pipeline.AddMany(src, decodebin)
|
pipeline.AddMany(src, decodebin)
|
||||||
src.Link(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
|
// 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.
|
// 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
|
// decodebin automatically adds a src-pad for this raw stream, which
|
||||||
// we can use to build the follow-up pipeline.
|
// 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
|
// Try to detect whether this is video or audio
|
||||||
var isAudio, isVideo bool
|
var isAudio, isVideo bool
|
||||||
caps := srcPad.GetCurrentCaps()
|
caps := srcPad.CurrentCaps()
|
||||||
for i := 0; i < caps.GetSize(); i++ {
|
for i := 0; i < int(caps.Size()); i++ {
|
||||||
st := caps.GetStructureAt(i)
|
st := caps.Structure(uint(i))
|
||||||
if strings.HasPrefix(st.Name(), "audio/") {
|
if strings.HasPrefix(st.Name(), "audio/") {
|
||||||
isAudio = true
|
isAudio = true
|
||||||
}
|
}
|
||||||
@@ -93,84 +93,74 @@ func buildPipeline() (*gst.Pipeline, error) {
|
|||||||
err := errors.New("could not detect media stream type")
|
err := errors.New("could not detect media stream type")
|
||||||
// We can send errors directly to the pipeline bus if they occur.
|
// We can send errors directly to the pipeline bus if they occur.
|
||||||
// These will be handled downstream.
|
// These will be handled downstream.
|
||||||
msg := gst.NewErrorMessage(self, gst.NewGError(1, err), fmt.Sprintf("Received caps: %s", caps.String()), nil)
|
msg := gst.NewMessageError(weakDecodeBin.Value(), err, fmt.Sprintf("Received caps: %s", caps.String()))
|
||||||
pipeline.GetPipelineBus().Post(msg)
|
pipeline.Bus().Post(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAudio {
|
if isAudio {
|
||||||
// decodebin found a raw audiostream, so we build the follow-up pipeline to
|
// decodebin found a raw audiostream, so we build the follow-up pipeline to
|
||||||
// play it on the default audio playback device (using autoaudiosink).
|
// 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 {
|
if err != nil {
|
||||||
// We can create custom errors (with optional structures) and send them to the pipeline bus.
|
msg := gst.NewMessageError(weakDecodeBin.Value(), err, "Could not create elements for audio pipeline")
|
||||||
// The first argument reflects the source of the error, the second is the error itself, followed by a debug string.
|
pipeline.Bus().Post(msg)
|
||||||
msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "Could not create elements for audio pipeline", nil)
|
|
||||||
pipeline.GetPipelineBus().Post(msg)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pipeline.AddMany(elements...)
|
pipeline.Add(audiosink)
|
||||||
gst.ElementLinkMany(elements...)
|
|
||||||
|
|
||||||
// !!ATTENTION!!:
|
// !!ATTENTION!!:
|
||||||
// This is quite important and people forget it often. Without making sure that
|
// 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.
|
// 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.
|
// 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
|
audiosink.SyncStateWithParent()
|
||||||
queue := elements[0]
|
|
||||||
// Get the queue element's sink pad and link the decodebin's newly created
|
// Get the queue element's sink pad and link the decodebin's newly created
|
||||||
// src pad for the audio stream to it.
|
// src pad for the audio stream to it.
|
||||||
sinkPad := queue.GetStaticPad("sink")
|
sinkPad := audiosink.StaticPad("sink")
|
||||||
srcPad.Link(sinkPad)
|
srcPad.Link(sinkPad)
|
||||||
|
|
||||||
} else if isVideo {
|
} else if isVideo {
|
||||||
// decodebin found a raw videostream, so we build the follow-up pipeline to
|
// decodebin found a raw videostream, so we build the follow-up pipeline to
|
||||||
// display it using the autovideosink.
|
// 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 {
|
if err != nil {
|
||||||
msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "Could not create elements for video pipeline", nil)
|
msg := gst.NewMessageError(weakDecodeBin.Value(), err, "Could not create elements for video pipeline")
|
||||||
pipeline.GetPipelineBus().Post(msg)
|
pipeline.Bus().Post(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pipeline.AddMany(elements...)
|
pipeline.Add(videosink)
|
||||||
gst.ElementLinkMany(elements...)
|
|
||||||
|
|
||||||
for _, e := range elements {
|
videosink.SyncStateWithParent()
|
||||||
e.SyncStateWithParent()
|
|
||||||
}
|
|
||||||
|
|
||||||
queue := elements[0]
|
|
||||||
// Get the queue element's sink pad and link the decodebin's newly created
|
// Get the queue element's sink pad and link the decodebin's newly created
|
||||||
// src pad for the video stream to it.
|
// src pad for the video stream to it.
|
||||||
sinkPad := queue.GetStaticPad("sink")
|
sinkPad := videosink.StaticPad("sink")
|
||||||
srcPad.Link(sinkPad)
|
srcPad.Link(sinkPad)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return pipeline, nil
|
return pipeline, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) error {
|
func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) {
|
||||||
// Start the pipeline
|
// Start the pipeline
|
||||||
pipeline.SetState(gst.StatePlaying)
|
pipeline.SetState(gst.StatePlaying)
|
||||||
|
|
||||||
// Add a message watch to the bus to quit on any error
|
// 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
|
var err error
|
||||||
|
|
||||||
// If the stream has ended or any element posts an error to the
|
// If the stream has ended or any element posts an error to the
|
||||||
// bus, populate error.
|
// bus, populate error.
|
||||||
switch msg.Type() {
|
switch msg.Type() {
|
||||||
case gst.MessageEOS:
|
case gst.MessageEos:
|
||||||
err = errors.New("end-of-stream")
|
err = errors.New("end-of-stream")
|
||||||
case gst.MessageError:
|
case gst.MessageError:
|
||||||
// The parsed error implements the error interface, but also
|
// The parsed error implements the error interface, but also
|
||||||
// contains additional debug information.
|
// contains additional debug information.
|
||||||
gerr := msg.ParseError()
|
gerr, debug := msg.ParseError()
|
||||||
fmt.Println("go-gst-debug:", gerr.DebugString())
|
fmt.Println("go-gst-debug:", debug)
|
||||||
err = gerr
|
err = gerr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +175,7 @@ func runPipeline(loop *glib.MainLoop, pipeline *gst.Pipeline) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Block on the main loop
|
// Block on the main loop
|
||||||
return loop.RunError()
|
loop.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -195,11 +185,14 @@ func main() {
|
|||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
examples.RunLoop(func(loop *glib.MainLoop) error {
|
|
||||||
pipeline, err := buildPipeline()
|
pipeline, err := buildPipeline()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
panic(err)
|
||||||
}
|
}
|
||||||
return runPipeline(loop, pipeline)
|
|
||||||
})
|
mainloop := glib.NewMainLoop(glib.MainContextDefault(), false)
|
||||||
|
|
||||||
|
runPipeline(mainloop, pipeline)
|
||||||
}
|
}
|
||||||
|
@@ -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 <uri>\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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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 <uri>\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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -24,22 +24,26 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-gst/go-glib/glib"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"github.com/go-gst/go-gst/examples"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
"github.com/go-gst/go-gst/gst"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPipeline(loop *glib.MainLoop) error {
|
func main() {
|
||||||
gst.Init(nil)
|
gst.Init()
|
||||||
|
|
||||||
|
mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false)
|
||||||
|
|
||||||
// Build a pipeline with fake audio data going to a fakesink
|
// 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 {
|
if err != nil {
|
||||||
return err
|
fmt.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pipeline := res.(*gst.Pipeline)
|
||||||
|
|
||||||
// Retrieve the message bus for the pipeline
|
// Retrieve the message bus for the pipeline
|
||||||
bus := pipeline.GetPipelineBus()
|
bus := pipeline.Bus()
|
||||||
|
|
||||||
// Start the pipeline
|
// Start the pipeline
|
||||||
pipeline.SetState(gst.StatePlaying)
|
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).
|
// 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
|
// Every message from the bus is passed through this function. Its return value determines
|
||||||
// whether the handler wants to be called again.
|
// whether the handler wants to be called again.
|
||||||
bus.AddWatch(func(msg *gst.Message) (cont bool) {
|
bus.AddWatch(0, func(_ *gst.Bus, msg *gst.Message) bool {
|
||||||
// Assume we are continuing
|
|
||||||
cont = true
|
|
||||||
|
|
||||||
switch msg.Type() {
|
switch msg.Type() {
|
||||||
case gst.MessageEOS:
|
case gst.MessageEos:
|
||||||
fmt.Println("Received EOS")
|
fmt.Println("Received EOS")
|
||||||
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
||||||
// to stop execution here.
|
// to stop execution here.
|
||||||
loop.Quit()
|
mainLoop.Quit()
|
||||||
case gst.MessageError:
|
case gst.MessageError:
|
||||||
err := msg.ParseError()
|
err, debug := msg.ParseError()
|
||||||
fmt.Println("ERROR:", err)
|
fmt.Println("ERROR:", err)
|
||||||
fmt.Println("DEBUG:", err.DebugString())
|
fmt.Println("DEBUG:", debug)
|
||||||
loop.Quit()
|
mainLoop.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Kick off a goroutine that after 5 seconds will send an eos event to the pipeline.
|
// 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
|
// 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,
|
// 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.
|
// essentially telling the application that the pipeline is completely drained.
|
||||||
pipeline.SendEvent(gst.NewEOSEvent())
|
pipeline.SendEvent(gst.NewEventEos())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Operate GStreamer's bus, facilliating GLib's mainloop here.
|
mainLoop.Run()
|
||||||
// This function call will block until you tell the mainloop to quit
|
|
||||||
// (see above for how to do this).
|
|
||||||
loop.Run()
|
|
||||||
|
|
||||||
// Stop the pipeline
|
return
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@@ -4,44 +4,28 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-gst/go-gst/examples"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
"github.com/go-gst/go-gst/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() {
|
func main() {
|
||||||
examples.Run(func() error {
|
gst.Init()
|
||||||
var err error
|
|
||||||
if err = start(); err != nil {
|
registry := gst.RegistryGet()
|
||||||
return err
|
|
||||||
|
higherThanHighRank := uint(258)
|
||||||
|
|
||||||
|
pluginf := registry.LookupFeature("vtdec_hw")
|
||||||
|
|
||||||
|
if pluginf == nil {
|
||||||
|
fmt.Printf("codec vtdec_hw not found")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
plugin := gst.BasePluginFeature(pluginf)
|
||||||
|
|
||||||
|
plugin.SetRank(higherThanHighRank)
|
||||||
|
|
||||||
|
rank := plugin.Rank()
|
||||||
|
fmt.Println("vtdec_hw rank is:", rank)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
@@ -4,46 +4,89 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-gst/go-glib/glib"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"github.com/go-gst/go-gst/examples"
|
"github.com/go-gst/go-gst/pkg/gst"
|
||||||
"github.com/go-gst/go-gst/gst"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPipeline(mainLoop *glib.MainLoop) error {
|
func main() {
|
||||||
if len(os.Args) == 1 {
|
gst.Init()
|
||||||
return errors.New("pipeline string cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
gst.Init(&os.Args)
|
mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false)
|
||||||
|
|
||||||
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
// 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 {
|
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.
|
// 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() {
|
switch msg.Type() {
|
||||||
case gst.MessageEOS: // When end-of-stream is received stop the main loop
|
case gst.MessageEos: // When end-of-stream is received stop the main loop
|
||||||
pipeline.BlockSetState(gst.StateNull)
|
pipeline.BlockSetState(gst.StateNull, gst.ClockTime(time.Second))
|
||||||
|
|
||||||
mainLoop.Quit()
|
mainLoop.Quit()
|
||||||
case gst.MessageError: // Error messages are always fatal
|
case gst.MessageError: // Error messages are always fatal
|
||||||
err := msg.ParseError()
|
err, debug := msg.ParseError()
|
||||||
fmt.Println("ERROR:", err.Error())
|
fmt.Println("ERROR:", err.Error())
|
||||||
if debug := err.DebugString(); debug != "" {
|
if debug != "" {
|
||||||
fmt.Println("DEBUG:", debug)
|
fmt.Println("DEBUG:", debug)
|
||||||
}
|
}
|
||||||
mainLoop.Quit()
|
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:
|
default:
|
||||||
// All messages implement a Stringer. However, this is
|
panic("unexpected gst.MessageType")
|
||||||
// typically an expensive thing to do and should be avoided.
|
|
||||||
fmt.Println(msg)
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -52,11 +95,8 @@ func runPipeline(mainLoop *glib.MainLoop) error {
|
|||||||
pipeline.SetState(gst.StatePlaying)
|
pipeline.SetState(gst.StatePlaying)
|
||||||
|
|
||||||
// Block on the main loop
|
// Block on the main loop
|
||||||
return mainLoop.RunError()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
mainLoop.Run()
|
||||||
examples.RunLoop(func(loop *glib.MainLoop) error {
|
|
||||||
return runPipeline(loop)
|
return
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
171
generator.go
171
generator.go
@@ -120,7 +120,7 @@ var Data = genmain.Overlay(
|
|||||||
MiniObjectExtenderBorrows(),
|
MiniObjectExtenderBorrows(),
|
||||||
},
|
},
|
||||||
Postprocessors: map[string][]girgen.Postprocessor{
|
Postprocessors: map[string][]girgen.Postprocessor{
|
||||||
"Gst-1": {ElementFactoryMakeWithProperties},
|
"Gst-1": {ElementFactoryMakeWithProperties, ElementBlockSetState, BinAddMany, ElementLinkMany, IteratorValues, StructureGoMarshal},
|
||||||
"GstMpegts-1": {GstUseUnstableAPI},
|
"GstMpegts-1": {GstUseUnstableAPI},
|
||||||
"GstWebRTC-1": {GstUseUnstableAPI, FixWebrtcPkgConfig},
|
"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_mpegts_descriptor_parse_dvb_frequency_list"),
|
||||||
types.AbsoluteFilter("C.gst_source_buffer_get_buffered"),
|
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
|
// 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"),
|
types.AbsoluteFilter("C.gst_audio_get_channel_reorder_map"),
|
||||||
|
|
||||||
@@ -252,6 +255,172 @@ func FixWebrtcPkgConfig(nsgen *girgen.NamespaceGenerator) error {
|
|||||||
return nil
|
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 {
|
func ElementFactoryMakeWithProperties(nsgen *girgen.NamespaceGenerator) error {
|
||||||
fg := nsgen.MakeFile("elementfactory.go")
|
fg := nsgen.MakeFile("elementfactory.go")
|
||||||
fg.Header().NeedsExternGLib()
|
fg.Header().NeedsExternGLib()
|
||||||
|
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/go-gst/go-gst
|
module github.com/go-gst/go-gst
|
||||||
|
|
||||||
go 1.23.2
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/diamondburned/gotk4 v0.3.1
|
github.com/diamondburned/gotk4 v0.3.1
|
||||||
|
13
gst/go.mod
Normal file
13
gst/go.mod
Normal file
@@ -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
|
||||||
|
)
|
10
gst/go.sum
Normal file
10
gst/go.sum
Normal file
@@ -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=
|
114
pkg/gst/gst.go
114
pkg/gst/gst.go
@@ -4,6 +4,8 @@ package gst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
_ "runtime/cgo"
|
_ "runtime/cgo"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -57313,6 +57315,118 @@ func ElementFactoryMakeWithProperties(factoryname string, properties map[string]
|
|||||||
return _element
|
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
|
// Init binds to the gst_init() function. Argument parsing is not
|
||||||
// supported.
|
// supported.
|
||||||
func Init() {
|
func Init() {
|
||||||
|
@@ -5434,42 +5434,6 @@ func (descriptor *Descriptor) ParseMetadataStd(metadataInputLeakRate *uint32, me
|
|||||||
return _ok
|
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
|
// ParseSatelliteDeliverySystem extracts the satellite delivery system
|
||||||
// information from descriptor.
|
// information from descriptor.
|
||||||
//
|
//
|
||||||
|
Reference in New Issue
Block a user