mirror of
https://github.com/go-gst/go-gst.git
synced 2025-09-26 20:11:18 +08:00
169 lines
4.5 KiB
Go
169 lines
4.5 KiB
Go
// 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)
|
|
})
|
|
}
|