From 8d5a0bbf3830fc720cd2f769e703236c4e4df9b9 Mon Sep 17 00:00:00 2001 From: Avi Zimmerman Date: Fri, 8 Jan 2021 22:00:38 +0200 Subject: [PATCH] add GstBaseSink and filesink example --- examples/plugins/filesink/filesink.go | 258 ++++++++++++++++++++++ examples/plugins/filesink/plugin.go | 46 ++++ examples/plugins/filesrc/filesrc.go | 5 - examples/plugins/filesrc/plugin.go | 2 +- go.sum | 2 - gst/base/doc.go | 3 + gst/base/gst.go.h | 8 +- gst/base/gst_base_sink.go | 306 ++++++++++++++++++++++++++ gst/base/gst_base_sink_exports.go | 166 ++++++++++++++ gst/base/gst_base_sink_impl.go | 240 ++++++++++++++++++++ gst/base/gst_base_src.go | 142 ++++++++++++ gst/base/util.go | 5 + gst/gst_allocator.go | 10 + gst/gst_buffer_list.go | 20 +- gst/gst_buffer_pool.go | 5 + gst/gst_bus.go | 3 +- gst/gst_sample.go | 49 ++++- gst/gst_structure.go | 6 + gst/gstauto/util.go | 4 +- 19 files changed, 1261 insertions(+), 19 deletions(-) create mode 100644 examples/plugins/filesink/filesink.go create mode 100644 examples/plugins/filesink/plugin.go create mode 100644 gst/base/gst_base_sink.go create mode 100644 gst/base/gst_base_sink_exports.go create mode 100644 gst/base/gst_base_sink_impl.go diff --git a/examples/plugins/filesink/filesink.go b/examples/plugins/filesink/filesink.go new file mode 100644 index 0000000..4c45a49 --- /dev/null +++ b/examples/plugins/filesink/filesink.go @@ -0,0 +1,258 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/tinyzimmer/go-glib/glib" + "github.com/tinyzimmer/go-gst/gst" + "github.com/tinyzimmer/go-gst/gst/base" +) + +// CAT is the log category for the gofilesink. It is safe to define GStreamer objects as globals +// without calling gst.Init, since in the context of a loaded plugin all initialization has +// already been taken care of by the loading application. +var CAT = gst.NewDebugCategory( + "gofilesink", + gst.DebugColorNone, + "GoFileSink Element", +) + +// Here we define a list of ParamSpecs that will make up the properties for our element. +// This element only has a single property, the location of the file to write to. +// When getting and setting properties later on, you will reference them by their index in +// this list. +var properties = []*gst.ParameterSpec{ + gst.NewStringParameter( + "location", // The name of the parameter + "File Location", // The long name for the parameter + "Location to write the file to", // A blurb about the parameter + nil, // A default value for the parameter + gst.ParameterReadWrite, // Flags for the parameter + ), +} + +// Here we declare a private struct to hold our internal state. +type state struct { + // Whether the element is started or not + started bool + // The file the element is writing to + file *os.File + // The current position in the file + position uint64 +} + +// This is another private struct where we hold the parameter values set on our +// element. +type settings struct { + location string +} + +// Finally a structure is defined that implements (at a minimum) the gst.GoElement interface. +// It is possible to signal to the bindings to inherit from other classes or implement other +// interfaces via the registration and TypeInit processes. +type fileSink struct { + // The settings for the element + settings *settings + // The current state of the element + state *state +} + +// setLocation is a simple method to check the validity of a provided file path and set the +// local value with it. +func (f *fileSink) setLocation(path string) error { + if f.state.started { + return errors.New("Changing the `location` property on a started `GoFileSink` is not supported") + } + f.settings.location = strings.TrimPrefix(path, "file://") // should obviously use url.URL and do actual parsing + return nil +} + +// The ObjectSubclass implementations below are for registering the various aspects of our +// element and its capabilities with the type system. These are the minimum methods that +// should be implemented by an element. + +// Every element needs to provide its own constructor that returns an initialized +// gst.GoElement implementation. Here we simply create a new fileSink with zeroed settings +// and state objects. +func (f *fileSink) New() gst.GoElement { + CAT.Log(gst.LevelLog, "Initializing new fileSink object") + return &fileSink{ + settings: &settings{}, + state: &state{}, + } +} + +// The TypeInit method should register any additional interfaces provided by the element. +// In this example we signal to the type system that we also implement the GstURIHandler interface. +func (f *fileSink) TypeInit(instance *gst.TypeInstance) { + CAT.Log(gst.LevelLog, "Adding URIHandler interface to type") + instance.AddInterface(gst.InterfaceURIHandler) +} + +// The ClassInit method should specify the metadata for this element and add any pad templates +// and properties. +func (f *fileSink) ClassInit(klass *gst.ElementClass) { + CAT.Log(gst.LevelLog, "Initializing gofilesink class") + klass.SetMetadata( + "File Sink", + "Sink/File", + "Write stream to a file", + "Avi Zimmerman ", + ) + CAT.Log(gst.LevelLog, "Adding sink pad template and properties to class") + klass.AddPadTemplate(gst.NewPadTemplate( + "sink", + gst.PadDirectionSink, + gst.PadPresenceAlways, + gst.NewAnyCaps(), + )) + klass.InstallProperties(properties) +} + +// Object implementations are used during the initialization of an element. The +// methods are called once the object is constructed and its properties are read +// and written to. These and the rest of the methods described below are documented +// in interfaces in the bindings, however only individual methods needs from those +// interfaces need to be implemented. When left unimplemented, the behavior of the parent +// class is inherited. + +// SetProperty is called when a `value` is set to the property at index `id` in the +// properties slice that we installed during ClassInit. It should attempt to register +// the value locally or signal any errors that occur in the process. +func (f *fileSink) SetProperty(self *gst.Object, id uint, value *glib.Value) { + param := properties[id] + switch param.Name() { + case "location": + var val string + if value == nil { + val = "" + } else { + val, _ = value.GetString() + } + if err := f.setLocation(val); err != nil { + gst.ToElement(self).ErrorMessage(gst.DomainLibrary, gst.LibraryErrorSettings, + fmt.Sprintf("Could not set location on object: %s", err.Error()), + "", + ) + return + } + self.Log(CAT, gst.LevelInfo, fmt.Sprintf("Set `location` to %s", f.settings.location)) + } +} + +// GetProperty is called to retrieve the value of the property at index `id` in the properties +// slice provided at ClassInit. +func (f *fileSink) GetProperty(self *gst.Object, id uint) *glib.Value { + param := properties[id] + switch param.Name() { + case "location": + if f.settings.location == "" { + return nil + } + val, err := glib.GValue(f.settings.location) + if err == nil { + return val + } + gst.ToElement(self).ErrorMessage(gst.DomainLibrary, gst.LibraryErrorFailed, + fmt.Sprintf("Could not convert %s to GValue", f.settings.location), + err.Error(), + ) + } + return nil +} + +// GstBaseSink implementations are optional methods to implement from the base.GstBaseSinkImpl interface. +// If the method is not overridden by the implementing struct, it will be inherited from the parent class. + +// Start is called to start the filesink. Open the file for writing and set the internal state. +func (f *fileSink) Start(self *base.GstBaseSink) bool { + if f.state.started { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "GoFileSink is already started", "") + return false + } + + if f.settings.location == "" { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "No location configured on the filesink", "") + return false + } + + destFile := f.settings.location + + var err error + f.state.file, err = os.Create(destFile) + if err != nil { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorOpenWrite, + fmt.Sprintf("Could not open %s for writing", destFile), err.Error()) + return false + } + + self.Log(CAT, gst.LevelDebug, fmt.Sprintf("Opened file %s for writing", destFile)) + + f.state.started = true + self.Log(CAT, gst.LevelInfo, "GoFileSink has started") + return true +} + +// Stop is called to stop the element. Set the internal state and close the file. +func (f *fileSink) Stop(self *base.GstBaseSink) bool { + if !f.state.started { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "GoFileSink is not started", "") + return false + } + + if err := f.state.file.Close(); err != nil { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorWrite, "Failed to close the destination file", err.Error()) + return false + } + + self.Log(CAT, gst.LevelInfo, "GoFileSink has stopped") + return true +} + +// Render is called when a buffer is ready to be written to the file. +func (f *fileSink) Render(self *base.GstBaseSink, buffer *gst.Buffer) gst.FlowReturn { + if !f.state.started { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorSettings, "GoFileSink is not started", "") + return gst.FlowError + } + + self.Log(CAT, gst.LevelTrace, fmt.Sprintf("Rendering buffer at %v", buffer.Instance())) + newPos, err := io.Copy(f.state.file, buffer.Reader()) + if err != nil { + self.ErrorMessage(gst.DomainResource, gst.ResourceErrorWrite, "Error copying buffer to file", err.Error()) + return gst.FlowError + } + + f.state.position += uint64(newPos) + self.Log(CAT, gst.LevelTrace, fmt.Sprintf("New position in file: %v", f.state.position)) + + return gst.FlowOK +} + +// URIHandler implementations are the methods required by the GstURIHandler interface. + +// GetURI returns the currently configured URI +func (f *fileSink) GetURI() string { return fmt.Sprintf("file://%s", f.settings.location) } + +// GetURIType returns the types of URI this element supports. +func (f *fileSink) GetURIType() gst.URIType { return gst.URISource } + +// GetProtocols returns the protcols this element supports. +func (f *fileSink) GetProtocols() []string { return []string{"file"} } + +// SetURI should set the URI that this element is working on. +func (f *fileSink) SetURI(uri string) (bool, error) { + if uri == "file://" { + return true, nil + } + err := f.setLocation(uri) + if err != nil { + return false, err + } + CAT.Log(gst.LevelInfo, fmt.Sprintf("Set `location` to %s via URIHandler", f.settings.location)) + return true, nil +} diff --git a/examples/plugins/filesink/plugin.go b/examples/plugins/filesink/plugin.go new file mode 100644 index 0000000..92bd104 --- /dev/null +++ b/examples/plugins/filesink/plugin.go @@ -0,0 +1,46 @@ +// The contents of this file could be generated from markers placed in filesink.go +package main + +import "C" + +import ( + "unsafe" + + "github.com/tinyzimmer/go-gst/gst" + "github.com/tinyzimmer/go-gst/gst/base" +) + +// The metadata for this plugin +var pluginMeta = &gst.PluginMetadata{ + MajorVersion: gst.VersionMajor, + MinorVersion: gst.VersionMinor, + Name: "go-filesink-plugin", + Description: "File plugins written in Go", + Version: "v0.0.1", + License: gst.LicenseLGPL, + Source: "go-gst", + Package: "examples", + Origin: "https://github.com/tinyzimmer/go-gst", + ReleaseDate: "2021-01-04", + // The init function is called to register elements provided + // by the plugin. + Init: func(plugin *gst.Plugin) bool { + return gst.RegisterElement( + plugin, + "gofilesink", // The name of the element + gst.RankNone, // The rank of the element + &fileSink{}, // The GoElement implementation for the element + base.ExtendsBaseSink, // The base subclass this element extends + ) + }, +} + +// A single method must be exported from the compiled library that provides for GStreamer +// to fetch the description and init function for this plugin. The name of the method +// must match the format gst_plugin_NAME_get_desc, where hyphens are replaced with underscores. + +//export gst_plugin_gofilesink_get_desc +func gst_plugin_gofilesink_get_desc() unsafe.Pointer { return pluginMeta.Export() } + +// main is left unimplemented since these files are compiled to c-shared. +func main() {} diff --git a/examples/plugins/filesrc/filesrc.go b/examples/plugins/filesrc/filesrc.go index cf2d095..ce7c226 100644 --- a/examples/plugins/filesrc/filesrc.go +++ b/examples/plugins/filesrc/filesrc.go @@ -78,11 +78,6 @@ type fileSrc struct { state *state } -func (f *fileSrc) PostMessage(self *gst.Element, msg *gst.Message) bool { - fmt.Println("Received message on the pipeline", msg) - return self.ParentPostMessage(msg) -} - // Private methods only used internally by the plugin // setLocation is a simple method to check the validity of a provided file path and set the diff --git a/examples/plugins/filesrc/plugin.go b/examples/plugins/filesrc/plugin.go index 4557e7b..bd9321a 100644 --- a/examples/plugins/filesrc/plugin.go +++ b/examples/plugins/filesrc/plugin.go @@ -14,7 +14,7 @@ import ( var pluginMeta = &gst.PluginMetadata{ MajorVersion: gst.VersionMajor, MinorVersion: gst.VersionMinor, - Name: "go-file-plugins", + Name: "go-filesrc-plugin", Description: "File plugins written in Go", Version: "v0.0.1", License: gst.LicenseLGPL, diff --git a/go.sum b/go.sum index 4d56c96..81c9865 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ 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= -github.com/tinyzimmer/go-glib v0.0.1 h1:pfsr7EbP23/cZPGlEpsmwtQXjdzYj0S+WhUgOALdsQI= -github.com/tinyzimmer/go-glib v0.0.1/go.mod h1:D5rd0CvYn1p7TBhwlwnBXHSr4d8lwEY9JImPQ66S+Bs= github.com/tinyzimmer/go-glib v0.0.2 h1:hdvjwrhcS6WrMMeqfxsf3e7/lOhV8gbVyJ9/sN3LfyY= github.com/tinyzimmer/go-glib v0.0.2/go.mod h1:D5rd0CvYn1p7TBhwlwnBXHSr4d8lwEY9JImPQ66S+Bs= diff --git a/gst/base/doc.go b/gst/base/doc.go index 1ffb439..7ba4b28 100644 --- a/gst/base/doc.go +++ b/gst/base/doc.go @@ -1,2 +1,5 @@ // Package base contains bindings for extendable GStreamer base objects. +// +// The objects and methods provided by this package are mostly for use in building +// plugins using the go-gst bindings. package base diff --git a/gst/base/gst.go.h b/gst/base/gst.go.h index e1e7c7b..f3971a6 100644 --- a/gst/base/gst.go.h +++ b/gst/base/gst.go.h @@ -2,8 +2,12 @@ #define __GST_BASE_GO_H__ #include +#include -inline GstBaseSrc * toGstBaseSrc (void *p) { return GST_BASE_SRC_CAST(p); }; -inline GstBaseSrcClass * toGstBaseSrcClass (void *p) { return (GstBaseSrcClass *)p; }; +inline GstBaseSink * toGstBaseSink (void *p) { return GST_BASE_SINK_CAST(p); } +inline GstBaseSrc * toGstBaseSrc (void *p) { return GST_BASE_SRC_CAST(p); } + +inline GstBaseSinkClass * toGstBaseSinkClass (void *p) { return (GstBaseSinkClass *)p; } +inline GstBaseSrcClass * toGstBaseSrcClass (void *p) { return (GstBaseSrcClass *)p; } #endif \ No newline at end of file diff --git a/gst/base/gst_base_sink.go b/gst/base/gst_base_sink.go new file mode 100644 index 0000000..9bd45d0 --- /dev/null +++ b/gst/base/gst_base_sink.go @@ -0,0 +1,306 @@ +package base + +/* +#include "gst.go.h" +*/ +import "C" + +import ( + "time" + "unsafe" + + "github.com/tinyzimmer/go-gst/gst" +) + +// GstBaseSink represents a GstBaseSink. +type GstBaseSink struct{ *gst.Element } + +// ToGstBaseSink returns a GstBaseSink object for the given object. +func ToGstBaseSink(obj *gst.Object) *GstBaseSink { + return &GstBaseSink{&gst.Element{Object: obj}} +} + +// wrapGstBaseSink wraps the given unsafe.Pointer in a GstBaseSink instance. +func wrapGstBaseSink(obj *C.GstBaseSink) *GstBaseSink { + return &GstBaseSink{gst.FromGstElementUnsafe(unsafe.Pointer(obj))} +} + +// Instance returns the underlying C GstBaseSrc instance +func (g *GstBaseSink) Instance() *C.GstBaseSink { + return C.toGstBaseSink(g.Unsafe()) +} + +// DoPreroll is for if the sink spawns its own thread for pulling buffers from upstream. +// It should call this method after it has pulled a buffer. If the element needed to preroll, +// this function will perform the preroll and will then block until the element state is changed. +// +// This function should be called with the PREROLL_LOCK held and the object that caused the preroll. +// +// Since the object will always be a gst.MiniObject (which is not implemented properly), this method will check +// against the provided types for structs known to be used in this context. The currently known options +// are events, messages, queries, structures, and buffers. If you come across a need to use this function with +// an unsupported type, feel free to raise an Issue or open a PR. +func (g *GstBaseSink) DoPreroll(obj interface{}) gst.FlowReturn { + miniobj := getPrerollObj(obj) + if miniobj == nil { + return gst.FlowError + } + return gst.FlowReturn(C.gst_base_sink_do_preroll(g.Instance(), miniobj)) +} + +func getPrerollObj(obj interface{}) *C.GstMiniObject { + switch obj.(type) { + case *gst.Event: + return (*C.GstMiniObject)(unsafe.Pointer(obj.(*gst.Event).Instance())) + case *gst.Buffer: + return (*C.GstMiniObject)(unsafe.Pointer(obj.(*gst.Buffer).Instance())) + case *gst.Message: + return (*C.GstMiniObject)(unsafe.Pointer(obj.(*gst.Message).Instance())) + case *gst.Query: + return (*C.GstMiniObject)(unsafe.Pointer(obj.(*gst.Query).Instance())) + case *gst.Structure: + return (*C.GstMiniObject)(unsafe.Pointer(obj.(*gst.Structure).Instance())) + default: + return nil + } +} + +// GetBlocksize gets the number of bytes that the sink will pull when it is operating in pull mode. +func (g *GstBaseSink) GetBlocksize() uint { return uint(C.gst_base_sink_get_blocksize(g.Instance())) } + +// GetDropOutOfSegment checks if sink is currently configured to drop buffers which are outside the current segment +func (g *GstBaseSink) GetDropOutOfSegment() bool { + return gobool(C.gst_base_sink_get_drop_out_of_segment(g.Instance())) +} + +// GetLastSample gets the last sample that arrived in the sink and was used for preroll or for rendering. +// This property can be used to generate thumbnails. +// +// The GstCaps on the sample can be used to determine the type of the buffer. Unref after usage. Sample will +// be nil if no buffer has arrived yet. +func (g *GstBaseSink) GetLastSample() *gst.Sample { + sample := C.gst_base_sink_get_last_sample(g.Instance()) + if sample == nil { + return nil + } + return gst.FromGstSampleUnsafe(unsafe.Pointer(sample)) +} + +// GetLatency gets the currently configured latency. +func (g *GstBaseSink) GetLatency() time.Duration { + return time.Duration(C.gst_base_sink_get_latency(g.Instance())) +} + +// GetMaxBitrate gets the maximum amount of bits per second the sink will render. +func (g *GstBaseSink) GetMaxBitrate() uint64 { + return uint64(C.gst_base_sink_get_max_bitrate(g.Instance())) +} + +// GetMaxLateness gets the max lateness value. +func (g *GstBaseSink) GetMaxLateness() int64 { + return int64(C.gst_base_sink_get_max_lateness(g.Instance())) +} + +// GetProcessingDeadline gets the processing deadline of the sink. +func (g *GstBaseSink) GetProcessingDeadline() time.Duration { + return time.Duration(C.gst_base_sink_get_processing_deadline(g.Instance())) +} + +// GetRenderDelay gets the render delay for the sink. +func (g *GstBaseSink) GetRenderDelay() time.Duration { + return time.Duration(C.gst_base_sink_get_render_delay(g.Instance())) +} + +// SinkStats represents the current statistics on a GstBaseSink. +type SinkStats struct { + AverageRate float64 + Dropped uint64 + Rendered uint64 +} + +// GetSinkStats returns various GstBaseSink statistics. +func (g *GstBaseSink) GetSinkStats() *SinkStats { + st := gst.FromGstStructureUnsafe(unsafe.Pointer(C.gst_base_sink_get_stats(g.Instance()))) + stats := &SinkStats{} + if avgRate, err := st.GetValue("average-rate"); err == nil { + stats.AverageRate = avgRate.(float64) + } + if dropped, err := st.GetValue("dropped"); err == nil { + stats.Dropped = dropped.(uint64) + } + if rendered, err := st.GetValue("rendered"); err == nil { + stats.Rendered = rendered.(uint64) + } + return stats +} + +// GetSync checks if the sink is currently configured to synchronize on the clock. +func (g *GstBaseSink) GetSync() bool { return gobool(C.gst_base_sink_get_sync(g.Instance())) } + +// GetThrottleTime gets the time that will be inserted between frames to control maximum buffers +// per second. +func (g *GstBaseSink) GetThrottleTime() uint64 { + return uint64(C.gst_base_sink_get_throttle_time(g.Instance())) +} + +// GetTsOffset gets the synchronization offset of sink. +func (g *GstBaseSink) GetTsOffset() time.Duration { + return time.Duration(C.gst_base_sink_get_ts_offset(g.Instance())) +} + +// IsAsyncEnabled checks if the sink is currently configured to perform asynchronous state changes to PAUSED. +func (g *GstBaseSink) IsAsyncEnabled() bool { + return gobool(C.gst_base_sink_is_async_enabled(g.Instance())) +} + +// IsLastSampleEnabled checks if the sink is currently configured to store the last received sample. +func (g *GstBaseSink) IsLastSampleEnabled() bool { + return gobool(C.gst_base_sink_is_last_sample_enabled(g.Instance())) +} + +// IsQoSEnabled checks if sink is currently configured to send QoS events upstream. +func (g *GstBaseSink) IsQoSEnabled() bool { + return gobool(C.gst_base_sink_is_qos_enabled(g.Instance())) +} + +// QueryLatency queries the sink for the latency parameters. The latency will be queried from the +// upstream elements. live will be TRUE if sink is configured to synchronize against the clock. +// upstreamLive will be TRUE if an upstream element is live. +// +// If both live and upstreamLive are TRUE, the sink will want to compensate for the latency introduced +// by the upstream elements by setting the minLatency to a strictly positive value. +// +// This function is mostly used by subclasses. +func (g *GstBaseSink) QueryLatency() (ok, live, upstreamLive bool, minLatency, maxLatency time.Duration) { + var glive, gupLive C.gboolean + var gmin, gmax C.GstClockTime + ret := C.gst_base_sink_query_latency(g.Instance(), &glive, &gupLive, &gmin, &gmax) + return gobool(ret), gobool(glive), gobool(gupLive), time.Duration(gmin), time.Duration(gmax) +} + +// SetAsyncEnabled configures sink to perform all state changes asynchronously. When async is disabled, +// the sink will immediately go to PAUSED instead of waiting for a preroll buffer. This feature is useful +// if the sink does not synchronize against the clock or when it is dealing with sparse streams. +func (g *GstBaseSink) SetAsyncEnabled(enabled bool) { + C.gst_base_sink_set_async_enabled(g.Instance(), gboolean(enabled)) +} + +// SetBlocksize sets the number of bytes this sink will pull when operating in pull mode. +func (g *GstBaseSink) SetBlocksize(blocksize uint) { + C.gst_base_sink_set_blocksize(g.Instance(), C.guint(blocksize)) +} + +// SetDropOutOfSegment configures sink to drop buffers which are outside the current segment. +func (g *GstBaseSink) SetDropOutOfSegment(drop bool) { + C.gst_base_sink_set_drop_out_of_segment(g.Instance(), gboolean(drop)) +} + +// SetLastSampleEnabled configures the sink to store the last received sample. +func (g *GstBaseSink) SetLastSampleEnabled(enabled bool) { + C.gst_base_sink_set_last_sample_enabled(g.Instance(), gboolean(enabled)) +} + +// SetMaxBitrate sets the maximum amount of bits per second the sink will render. +func (g *GstBaseSink) SetMaxBitrate(bitrate uint64) { + C.gst_base_sink_set_max_bitrate(g.Instance(), C.guint64(bitrate)) +} + +// SetMaxLateness sets the new max lateness value to max_lateness. This value is used to decide if +// a buffer should be dropped or not based on the buffer timestamp and the current clock time. A +// value of -1 means an unlimited time. +func (g *GstBaseSink) SetMaxLateness(maxLateness int64) { + C.gst_base_sink_set_max_lateness(g.Instance(), C.gint64(maxLateness)) +} + +// SetProcessingDeadline sets the maximum amount of time (in nanoseconds) that the pipeline can take +// for processing the buffer. This is added to the latency of live pipelines. +// +// This function is usually called by subclasses. +func (g *GstBaseSink) SetProcessingDeadline(deadline time.Duration) { + C.gst_base_sink_set_processing_deadline(g.Instance(), C.GstClockTime(deadline.Nanoseconds())) +} + +// SetQoSEnabled configures sink to send Quality-of-Service events upstream. +func (g *GstBaseSink) SetQoSEnabled(enabled bool) { + C.gst_base_sink_set_qos_enabled(g.Instance(), gboolean(enabled)) +} + +// SetRenderDelay sets the render delay in sink to delay. The render delay is the time between actual +// rendering of a buffer and its synchronisation time. Some devices might delay media rendering which +// can be compensated for with this function. +// +// After calling this function, this sink will report additional latency and other sinks will adjust +// their latency to delay the rendering of their media. +// +// This function is usually called by subclasses. +func (g *GstBaseSink) SetRenderDelay(delay time.Duration) { + C.gst_base_sink_set_render_delay(g.Instance(), C.GstClockTime(delay.Nanoseconds())) +} + +// SetSync configures sink to synchronize on the clock or not. When sync is FALSE, incoming samples will +// be played as fast as possible. If sync is TRUE, the timestamps of the incoming buffers will be used to +// schedule the exact render time of its contents. +func (g *GstBaseSink) SetSync(sync bool) { C.gst_base_sink_set_sync(g.Instance(), gboolean(sync)) } + +// SetThrottleTime sets the time that will be inserted between rendered buffers. This can be used to control +// the maximum buffers per second that the sink will render. +func (g *GstBaseSink) SetThrottleTime(throttle uint64) { + C.gst_base_sink_set_throttle_time(g.Instance(), C.guint64(throttle)) +} + +// SetTsOffset adjusts the synchronization of sink with offset. A negative value will render buffers earlier +// than their timestamp. A positive value will delay rendering. This function can be used to fix playback of +// badly timestamped buffers. +func (g *GstBaseSink) SetTsOffset(offset time.Duration) { + C.gst_base_sink_set_ts_offset(g.Instance(), C.GstClockTimeDiff(offset.Nanoseconds())) +} + +// Wait will wait for preroll to complete and will then block until timeout is reached. It is usually called by +// subclasses that use their own internal synchronization but want to let some synchronization (like EOS) be +// handled by the base class. +// +// This function should only be called with the PREROLL_LOCK held (like when receiving an EOS event in the +// ::event vmethod or when handling buffers in ::render). +// +// The timeout argument should be the running_time of when the timeout should happen and will be adjusted with any +// latency and offset configured in the sink. +func (g *GstBaseSink) Wait(timeout time.Duration) (ret gst.FlowReturn, jitter time.Duration) { + var jit C.GstClockTimeDiff + gret := C.gst_base_sink_wait(g.Instance(), C.GstClockTime(timeout.Nanoseconds()), &jit) + return gst.FlowReturn(gret), time.Duration(jit) +} + +// WaitClock will block until timeout is reached. It is usually called by subclasses that use their own +// internal synchronization. +// +// If time is not valid, no synchronisation is done and GST_CLOCK_BADTIME is returned. Likewise, if synchronization +// is disabled in the element or there is no clock, no synchronization is done and GST_CLOCK_BADTIME is returned. +// +// This function should only be called with the PREROLL_LOCK held, like when receiving an EOS event in the event() +// vmethod or when receiving a buffer in the render() vmethod. +// +// The timeout argument should be the running_time of when this method should return and is not adjusted with any +// latency or offset configured in the sink. +func (g *GstBaseSink) WaitClock(timeout time.Duration) (ret gst.ClockReturn, jitter time.Duration) { + var jit C.GstClockTimeDiff + gret := C.gst_base_sink_wait_clock(g.Instance(), C.GstClockTime(timeout.Nanoseconds()), &jit) + return gst.ClockReturn(gret), time.Duration(jit) +} + +// WaitPreroll will block until the preroll is complete. +// +// If the render() method performs its own synchronisation against the clock it must unblock when going from +// PLAYING to the PAUSED state and call this method before continuing to render the remaining data. +// +// If the render() method can block on something else than the clock, it must also be ready to unblock immediately +// on the unlock() method and cause the render() method to immediately call this function. In this case, the +// subclass must be prepared to continue rendering where it left off if this function returns GST_FLOW_OK. +// +// This function will block until a state change to PLAYING happens (in which case this function returns +// GST_FLOW_OK) or the processing must be stopped due to a state change to READY or a FLUSH event (in which case +// this function returns GST_FLOW_FLUSHING). +// +// This function should only be called with the PREROLL_LOCK held, like in the render function. +func (g *GstBaseSink) WaitPreroll() gst.FlowReturn { + return gst.FlowReturn(C.gst_base_sink_wait_preroll(g.Instance())) +} diff --git a/gst/base/gst_base_sink_exports.go b/gst/base/gst_base_sink_exports.go new file mode 100644 index 0000000..865dc4d --- /dev/null +++ b/gst/base/gst_base_sink_exports.go @@ -0,0 +1,166 @@ +package base + +/* +#include "gst.go.h" +*/ +import "C" +import ( + "time" + "unsafe" + + "github.com/tinyzimmer/go-gst/gst" +) + +//export goGstBaseSinkActivatePull +func goGstBaseSinkActivatePull(sink *C.GstBaseSink, active C.gboolean) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + ActivatePull(self *GstBaseSink, active bool) bool + }) + return gboolean(iface.ActivatePull(wrapGstBaseSink(sink), gobool(active))) +} + +//export goGstBaseSinkEvent +func goGstBaseSinkEvent(sink *C.GstBaseSink, event *C.GstEvent) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Event(self *GstBaseSink, event *gst.Event) bool + }) + return gboolean(iface.Event(wrapGstBaseSink(sink), gst.FromGstEventUnsafe(unsafe.Pointer(event)))) +} + +//export goGstBaseSinkFixate +func goGstBaseSinkFixate(sink *C.GstBaseSink, caps *C.GstCaps) *C.GstCaps { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Fixate(self *GstBaseSink, caps *gst.Caps) *gst.Caps + }) + fixated := iface.Fixate(wrapGstBaseSink(sink), gst.FromGstCapsUnsafe(unsafe.Pointer(caps))) + if fixated == nil { + return nil + } + return (*C.GstCaps)(unsafe.Pointer(fixated.Instance())) +} + +//export goGstBaseSinkGetCaps +func goGstBaseSinkGetCaps(sink *C.GstBaseSink, filter *C.GstCaps) *C.GstCaps { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + GetCaps(self *GstBaseSink, filter *gst.Caps) *gst.Caps + }) + filtered := iface.GetCaps(wrapGstBaseSink(sink), gst.FromGstCapsUnsafe(unsafe.Pointer(filter))) + if filtered == nil { + return nil + } + return (*C.GstCaps)(unsafe.Pointer(filtered.Instance())) +} + +//export goGstBaseSinkGetTimes +func goGstBaseSinkGetTimes(sink *C.GstBaseSink, buf *C.GstBuffer, start, end *C.GstClockTime) { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + GetTimes(self *GstBaseSink, buffer *gst.Buffer) (start, end time.Duration) + }) + retStart, retEnd := iface.GetTimes(wrapGstBaseSink(sink), gst.FromGstBufferUnsafe(unsafe.Pointer(buf))) + *start = C.GstClockTime(retStart.Nanoseconds()) + *end = C.GstClockTime(retEnd.Nanoseconds()) +} + +//export goGstBaseSinkPrepare +func goGstBaseSinkPrepare(sink *C.GstBaseSink, buf *C.GstBuffer) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Prepare(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }) + return C.GstFlowReturn(iface.Prepare(wrapGstBaseSink(sink), gst.FromGstBufferUnsafe(unsafe.Pointer(buf)))) +} + +//export goGstBaseSinkPrepareList +func goGstBaseSinkPrepareList(sink *C.GstBaseSink, list *C.GstBufferList) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + PrepareList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + }) + return C.GstFlowReturn(iface.PrepareList(wrapGstBaseSink(sink), gst.FromGstBufferListUnsafe(unsafe.Pointer(list)))) +} + +//export goGstBaseSinkPreroll +func goGstBaseSinkPreroll(sink *C.GstBaseSink, buf *C.GstBuffer) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Preroll(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }) + return C.GstFlowReturn(iface.Preroll(wrapGstBaseSink(sink), gst.FromGstBufferUnsafe(unsafe.Pointer(buf)))) +} + +//export goGstBaseSinkProposeAllocation +func goGstBaseSinkProposeAllocation(sink *C.GstBaseSink, query *C.GstQuery) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + ProposeAllocation(self *GstBaseSink, query *gst.Query) bool + }) + return gboolean(iface.ProposeAllocation(wrapGstBaseSink(sink), gst.FromGstQueryUnsafe(unsafe.Pointer(query)))) +} + +//export goGstBaseSinkQuery +func goGstBaseSinkQuery(sink *C.GstBaseSink, query *C.GstQuery) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Query(self *GstBaseSink, query *gst.Query) bool + }) + return gboolean(iface.Query(wrapGstBaseSink(sink), gst.FromGstQueryUnsafe(unsafe.Pointer(query)))) +} + +//export goGstBaseSinkRender +func goGstBaseSinkRender(sink *C.GstBaseSink, buf *C.GstBuffer) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Render(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }) + return C.GstFlowReturn(iface.Render(wrapGstBaseSink(sink), gst.FromGstBufferUnsafe(unsafe.Pointer(buf)))) +} + +//export goGstBaseSinkRenderList +func goGstBaseSinkRenderList(sink *C.GstBaseSink, buf *C.GstBufferList) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + RenderList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + }) + return C.GstFlowReturn(iface.RenderList(wrapGstBaseSink(sink), gst.FromGstBufferListUnsafe(unsafe.Pointer(buf)))) +} + +//export goGstBaseSinkSetCaps +func goGstBaseSinkSetCaps(sink *C.GstBaseSink, caps *C.GstCaps) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + SetCaps(self *GstBaseSink, caps *gst.Caps) bool + }) + return gboolean(iface.SetCaps(wrapGstBaseSink(sink), gst.FromGstCapsUnsafe(unsafe.Pointer(caps)))) +} + +//export goGstBaseSinkStart +func goGstBaseSinkStart(sink *C.GstBaseSink) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Start(self *GstBaseSink) bool + }) + return gboolean(iface.Start(wrapGstBaseSink(sink))) +} + +//export goGstBaseSinkStop +func goGstBaseSinkStop(sink *C.GstBaseSink) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Stop(self *GstBaseSink) bool + }) + return gboolean(iface.Stop(wrapGstBaseSink(sink))) +} + +//export goGstBaseSinkUnlock +func goGstBaseSinkUnlock(sink *C.GstBaseSink) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + Unlock(self *GstBaseSink) bool + }) + return gboolean(iface.Unlock(wrapGstBaseSink(sink))) +} + +//export goGstBaseSinkUnlockStop +func goGstBaseSinkUnlockStop(sink *C.GstBaseSink) C.gboolean { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + UnlockStop(self *GstBaseSink) bool + }) + return gboolean(iface.UnlockStop(wrapGstBaseSink(sink))) +} + +//export goGstBaseSinkWaitEvent +func goGstBaseSinkWaitEvent(sink *C.GstBaseSink, event *C.GstEvent) C.GstFlowReturn { + iface := gst.FromObjectUnsafePrivate(unsafe.Pointer(sink)).(interface { + WaitEvent(self *GstBaseSink, event *gst.Event) gst.FlowReturn + }) + return C.GstFlowReturn(iface.WaitEvent(wrapGstBaseSink(sink), gst.FromGstEventUnsafe(unsafe.Pointer(event)))) +} diff --git a/gst/base/gst_base_sink_impl.go b/gst/base/gst_base_sink_impl.go new file mode 100644 index 0000000..9b36e0b --- /dev/null +++ b/gst/base/gst_base_sink_impl.go @@ -0,0 +1,240 @@ +package base + +/* +#include "gst.go.h" + +extern gboolean goGstBaseSinkActivatePull (GstBaseSink * sink, gboolean active); +extern gboolean goGstBaseSinkEvent (GstBaseSink * sink, GstEvent * event); +extern GstCaps * goGstBaseSinkFixate (GstBaseSink * sink, GstCaps * caps); +extern GstCaps * goGstBaseSinkGetCaps (GstBaseSink * sink, GstCaps * filter); +extern void goGstBaseSinkGetTimes (GstBaseSink * sink, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); +extern GstFlowReturn goGstBaseSinkPrepare (GstBaseSink * sink, GstBuffer * buffer); +extern GstFlowReturn goGstBaseSinkPrepareList (GstBaseSink * sink, GstBufferList * buffer_list); +extern GstFlowReturn goGstBaseSinkPreroll (GstBaseSink * sink, GstBuffer * buffer); +extern gboolean goGstBaseSinkProposeAllocation (GstBaseSink * sink, GstQuery * query); +extern gboolean goGstBaseSinkQuery (GstBaseSink * sink, GstQuery * query); +extern GstFlowReturn goGstBaseSinkRender (GstBaseSink * sink, GstBuffer * buffer); +extern GstFlowReturn goGstBaseSinkRenderList (GstBaseSink * sink, GstBufferList * buffer_list); +extern gboolean goGstBaseSinkSetCaps (GstBaseSink * sink, GstCaps * caps); +extern gboolean goGstBaseSinkStart (GstBaseSink * sink); +extern gboolean goGstBaseSinkStop (GstBaseSink * sink); +extern gboolean goGstBaseSinkUnlock (GstBaseSink * sink); +extern gboolean goGstBaseSinkUnlockStop (GstBaseSink * sink); +extern GstFlowReturn goGstBaseSinkWaitEvent (GstBaseSink * sink, GstEvent * event); + +GstFlowReturn do_wait_event (GstBaseSink * sink, GstEvent * event) +{ + GObjectClass * this_class = G_OBJECT_GET_CLASS(G_OBJECT(sink)); + GstBaseSinkClass * parent = toGstBaseSinkClass(g_type_class_peek_parent(this_class)); + GstFlowReturn ret = parent->wait_event(sink, event); + if (ret == GST_FLOW_ERROR) + return ret; + return goGstBaseSinkWaitEvent(sink, event); +} + +void setGstBaseSinkActivatePull (GstBaseSinkClass * klass) { klass->activate_pull = goGstBaseSinkActivatePull; } +void setGstBaseSinkEvent (GstBaseSinkClass * klass) { klass->event = goGstBaseSinkEvent; } +void setGstBaseSinkFixate (GstBaseSinkClass * klass) { klass->fixate = goGstBaseSinkFixate; } +void setGstBaseSinkGetCaps (GstBaseSinkClass * klass) { klass->get_caps = goGstBaseSinkGetCaps; } +void setGstBaseSinkGetTimes (GstBaseSinkClass * klass) { klass->get_times = goGstBaseSinkGetTimes; } +void setGstBaseSinkPrepare (GstBaseSinkClass * klass) { klass->prepare = goGstBaseSinkPrepare; } +void setGstBaseSinkPrepareList (GstBaseSinkClass * klass) { klass->prepare_list = goGstBaseSinkPrepareList; } +void setGstBaseSinkPreroll (GstBaseSinkClass * klass) { klass->preroll = goGstBaseSinkPreroll; } +void setGstBaseSinkProposeAllocation (GstBaseSinkClass * klass) { klass->propose_allocation = goGstBaseSinkProposeAllocation; } +void setGstBaseSinkQuery (GstBaseSinkClass * klass) { klass->query = goGstBaseSinkQuery; } +void setGstBaseSinkRender (GstBaseSinkClass * klass) { klass->render = goGstBaseSinkRender; } +void setGstBaseSinkRenderList (GstBaseSinkClass * klass) { klass->render_list = goGstBaseSinkRenderList; } +void setGstBaseSinkSetCaps (GstBaseSinkClass * klass) { klass->set_caps = goGstBaseSinkSetCaps; } +void setGstBaseSinkStart (GstBaseSinkClass * klass) { klass->start = goGstBaseSinkStart; } +void setGstBaseSinkStop (GstBaseSinkClass * klass) { klass->stop = goGstBaseSinkStop; } +void setGstBaseSinkUnlock (GstBaseSinkClass * klass) { klass->unlock = goGstBaseSinkUnlock; } +void setGstBaseSinkUnlockStop (GstBaseSinkClass * klass) { klass->unlock_stop = goGstBaseSinkUnlockStop; } +void setGstBaseSinkWaitEvent (GstBaseSinkClass * klass) { klass->wait_event = do_wait_event; } + +*/ +import "C" +import ( + "time" + "unsafe" + + "github.com/tinyzimmer/go-glib/glib" + "github.com/tinyzimmer/go-gst/gst" +) + +var ( + // ExtendsBaseSink is an Extendable for extending a GstBaseSink + ExtendsBaseSink gst.Extendable = &extendsBaseSink{parent: gst.ExtendsElement} +) + +// GstBaseSinkImpl is the documented interface for extending a GstBaseSink. It does not have to +// be implemented in it's entirety. Each of the methods it declares will be checked for their presence +// in the initializing object, and if the object declares an override it will replace the default +// implementation in the virtual methods. +type GstBaseSinkImpl interface { + // Subclasses should override this when they can provide an alternate method of spawning a thread to + // drive the pipeline in pull mode. Should start or stop the pulling thread, depending on the value + // of the "active" argument. Called after actually activating the sink pad in pull mode. The default + // implementation starts a task on the sink pad. + ActivatePull(self *GstBaseSink, active bool) bool + // Override this to handle events arriving on the sink pad + Event(self *GstBaseSink, event *gst.Event) bool + // Only useful in pull mode. Implement if you have ideas about what should be the default values for + // the caps you support. + Fixate(self *GstBaseSink, caps *gst.Caps) *gst.Caps + // Called to get sink pad caps from the subclass + GetCaps(self *GstBaseSink, filter *gst.Caps) *gst.Caps + // Called to get the start and end times for synchronising the passed buffer to the clock + GetTimes(self *GstBaseSink, buffer *gst.Buffer) (start, end time.Duration) + // Called to prepare the buffer for render and preroll. This function is called before synchronization + // is performed. + Prepare(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + // Called to prepare the buffer list for render_list. This function is called before synchronization is + // performed. + PrepareList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + // Called to present the preroll buffer if desired. + Preroll(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + // Used to configure the allocation query + ProposeAllocation(self *GstBaseSink, query *gst.Query) bool + // Handle queries on the element + Query(self *GstBaseSink, query *gst.Query) bool + // Called when a buffer should be presented or output, at the correct moment if the GstBaseSink has been + // set to sync to the clock. + Render(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + // Same as render but used with buffer lists instead of buffers. + RenderList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + // Notify subclass of changed caps + SetCaps(self *GstBaseSink, caps *gst.Caps) bool + // Start processing. Ideal for opening resources in the subclass + Start(self *GstBaseSink) bool + // Stop processing. Subclasses should use this to close resources. + Stop(self *GstBaseSink) bool + // Unlock any pending access to the resource. Subclasses should unblock any blocked function ASAP and call + // WaitPreroll + Unlock(self *GstBaseSink) bool + // Clear the previous unlock request. Subclasses should clear any state they set during Unlock(), and be ready + // to continue where they left off after WaitPreroll, Wait or WaitClock return or Render() is called again. + UnlockStop(self *GstBaseSink) bool + // Override this to implement custom logic to wait for the event time (for events like EOS and GAP). The bindings + // take care of first chaining up to the parent class. + WaitEvent(self *GstBaseSink, event *gst.Event) gst.FlowReturn +} + +type extendsBaseSink struct{ parent gst.Extendable } + +func (e *extendsBaseSink) Type() glib.Type { return glib.Type(C.gst_base_sink_get_type()) } +func (e *extendsBaseSink) ClassSize() int64 { return int64(C.sizeof_GstBaseSinkClass) } +func (e *extendsBaseSink) InstanceSize() int64 { return int64(C.sizeof_GstBaseSink) } + +func (e *extendsBaseSink) InitClass(klass unsafe.Pointer, elem gst.GoElement) { + e.parent.InitClass(klass, elem) + + sinkClass := C.toGstBaseSinkClass(klass) + + if _, ok := elem.(interface { + ActivatePull(self *GstBaseSink, active bool) bool + }); ok { + C.setGstBaseSinkActivatePull(sinkClass) + } + if _, ok := elem.(interface { + Event(self *GstBaseSink, event *gst.Event) bool + }); ok { + C.setGstBaseSinkEvent(sinkClass) + } + + if _, ok := elem.(interface { + Fixate(self *GstBaseSink, caps *gst.Caps) *gst.Caps + }); ok { + C.setGstBaseSinkFixate(sinkClass) + } + + if _, ok := elem.(interface { + GetCaps(self *GstBaseSink, filter *gst.Caps) *gst.Caps + }); ok { + C.setGstBaseSinkGetCaps(sinkClass) + } + + if _, ok := elem.(interface { + GetTimes(self *GstBaseSink, buffer *gst.Buffer) (start, end time.Duration) + }); ok { + C.setGstBaseSinkGetTimes(sinkClass) + } + + if _, ok := elem.(interface { + Prepare(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }); ok { + C.setGstBaseSinkPrepare(sinkClass) + } + + if _, ok := elem.(interface { + PrepareList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + }); ok { + C.setGstBaseSinkPrepareList(sinkClass) + } + + if _, ok := elem.(interface { + Preroll(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }); ok { + C.setGstBaseSinkPreroll(sinkClass) + } + + if _, ok := elem.(interface { + ProposeAllocation(self *GstBaseSink, query *gst.Query) bool + }); ok { + C.setGstBaseSinkProposeAllocation(sinkClass) + } + + if _, ok := elem.(interface { + Query(self *GstBaseSink, query *gst.Query) bool + }); ok { + C.setGstBaseSinkQuery(sinkClass) + } + + if _, ok := elem.(interface { + Render(self *GstBaseSink, buffer *gst.Buffer) gst.FlowReturn + }); ok { + C.setGstBaseSinkRender(sinkClass) + } + + if _, ok := elem.(interface { + RenderList(self *GstBaseSink, bufferList *gst.BufferList) gst.FlowReturn + }); ok { + C.setGstBaseSinkRenderList(sinkClass) + } + + if _, ok := elem.(interface { + SetCaps(self *GstBaseSink, caps *gst.Caps) bool + }); ok { + C.setGstBaseSinkSetCaps(sinkClass) + } + + if _, ok := elem.(interface { + Start(self *GstBaseSink) bool + }); ok { + C.setGstBaseSinkStart(sinkClass) + } + + if _, ok := elem.(interface { + Stop(self *GstBaseSink) bool + }); ok { + C.setGstBaseSinkStop(sinkClass) + } + + if _, ok := elem.(interface { + Unlock(self *GstBaseSink) bool + }); ok { + C.setGstBaseSinkUnlock(sinkClass) + } + + if _, ok := elem.(interface { + UnlockStop(self *GstBaseSink) bool + }); ok { + C.setGstBaseSinkUnlockStop(sinkClass) + } + + if _, ok := elem.(interface { + WaitEvent(self *GstBaseSink, event *gst.Event) gst.FlowReturn + }); ok { + C.setGstBaseSinkWaitEvent(sinkClass) + } + +} diff --git a/gst/base/gst_base_src.go b/gst/base/gst_base_src.go index 653721f..dc587db 100644 --- a/gst/base/gst_base_src.go +++ b/gst/base/gst_base_src.go @@ -5,6 +5,7 @@ package base */ import "C" import ( + "time" "unsafe" "github.com/tinyzimmer/go-gst/gst" @@ -28,6 +29,106 @@ func (g *GstBaseSrc) Instance() *C.GstBaseSrc { return C.toGstBaseSrc(g.Unsafe()) } +// GetAllocator retrieves the memory allocator used by this base src. Unref after usage. +func (g *GstBaseSrc) GetAllocator() (*gst.Allocator, *gst.AllocationParams) { + var allocParams C.GstAllocationParams + var allocator *C.GstAllocator + C.gst_base_src_get_allocator(g.Instance(), &allocator, &allocParams) + return gst.FromGstAllocatorUnsafe(unsafe.Pointer(allocator)), gst.FromGstAllocationParamsUnsafe(unsafe.Pointer(&allocParams)) +} + +// GetBlocksize returns the number of bytes that the source will push out with each buffer. +func (g *GstBaseSrc) GetBlocksize() uint { return uint(C.gst_base_src_get_blocksize(g.Instance())) } + +// GetBufferPool returns the BufferPool used by this source. Unref after usage. +func (g *GstBaseSrc) GetBufferPool() *gst.BufferPool { + return gst.FromGstBufferPoolUnsafe(unsafe.Pointer(C.gst_base_src_get_buffer_pool(g.Instance()))) +} + +// DoTimestamp will query if the timestamps outgoing on this source's buffers are based on the current +// running time. +func (g *GstBaseSrc) DoTimestamp() bool { + return gobool(C.gst_base_src_get_do_timestamp(g.Instance())) +} + +// IsAsync retrieves the current async behavior of the source. +func (g *GstBaseSrc) IsAsync() bool { return gobool(C.gst_base_src_is_async(g.Instance())) } + +// IsLive checks if this source is in live mode. +func (g *GstBaseSrc) IsLive() bool { return gobool(C.gst_base_src_is_live(g.Instance())) } + +// Negotiate negotiates this source's pad caps with downstream elements. Do not call this in the Fill() +// vmethod. Call this in Create() or in Alloc(), before any buffer is allocated. +func (g *GstBaseSrc) Negotiate() bool { return gobool(C.gst_base_src_negotiate(g.Instance())) } + +// NewSegment prepares a new segment for emission downstream. This function must only be called by derived +// sub-classes, and only from the create function, as the stream-lock needs to be held. +// +// The format for the segment must be identical with the current format of the source, as configured with +// SetFormat. +// +// The format of src must not be gst.FormatUndefined and the format should be configured via SetFormat before +// calling this method. +func (g *GstBaseSrc) NewSegment(segment *gst.Segment) bool { + return gobool(C.gst_base_src_new_segment(g.Instance(), (*C.GstSegment)(unsafe.Pointer(segment.Instance())))) +} + +// QueryLatency queries the source for the latency parameters. live will be TRUE when src is configured as a +// live source. minLatency and maxLatency will be set to the difference between the running time and the timestamp +// of the first buffer. +// +// This function is mostly used by subclasses. +func (g *GstBaseSrc) QueryLatency() (ok, live bool, minLatency, maxLatency time.Duration) { + var glive C.gboolean + var gmin C.GstClockTime + var gmax C.GstClockTime + gok := C.gst_base_src_query_latency(g.Instance(), &glive, &gmin, &gmax) + return gobool(gok), gobool(glive), time.Duration(gmin), time.Duration(gmax) +} + +// SetAsync configures async behaviour in src, no state change will block. The open, close, start, stop, play and +// pause virtual methods will be executed in a different thread and are thus allowed to perform blocking operations. +// Any blocking operation should be unblocked with the unlock vmethod. +func (g *GstBaseSrc) SetAsync(async bool) { C.gst_base_src_set_async(g.Instance(), gboolean(async)) } + +// SetAutomaticEOS sets whether EOS should be automatically emmitted. +// +// If automaticEOS is TRUE, src will automatically go EOS if a buffer after the total size is returned. By default +// this is TRUE but sources that can't return an authoritative size and only know that they're EOS when trying to +// read more should set this to FALSE. +// +// When src operates in gst.FormatTime, GstBaseSrc will send an EOS when a buffer outside of the currently configured +// segment is pushed if automaticEOS is TRUE. Since 1.16, if automatic_eos is FALSE an EOS will be pushed only when +// the Create() implementation returns gst.FlowEOS. +func (g *GstBaseSrc) SetAutomaticEOS(automaticEOS bool) { + C.gst_base_src_set_automatic_eos(g.Instance(), gboolean(automaticEOS)) +} + +// SetBlocksize sets the number of bytes that src will push out with each buffer. When blocksize is set to -1, a +// default length will be used. +func (g *GstBaseSrc) SetBlocksize(size uint) { + C.gst_base_src_set_blocksize(g.Instance(), C.guint(size)) +} + +// SetCaps sets new caps on the source pad. +func (g *GstBaseSrc) SetCaps(caps *gst.Caps) bool { + return gobool(C.gst_base_src_set_caps(g.Instance(), (*C.GstCaps)(unsafe.Pointer(caps.Instance())))) +} + +// SetDoTimestamp configures src to automatically timestamp outgoing buffers based on the current running_time of the pipeline. +// This property is mostly useful for live sources. +func (g *GstBaseSrc) SetDoTimestamp(doTimestamp bool) { + C.gst_base_src_set_do_timestamp(g.Instance(), gboolean(doTimestamp)) +} + +// SetDynamicSize sets if the size is dynamic for this source. +// +// If not dynamic, size is only updated when needed, such as when trying to read past current tracked size. Otherwise, size is +// checked for upon each read. +func (g *GstBaseSrc) SetDynamicSize(dynamic bool) { + C.gst_base_src_set_dynamic_size(g.Instance(), gboolean(dynamic)) +} + // SetFormat sets the default format of the source. This will be the format used for sending // SEGMENT events and for performing seeks. // @@ -39,9 +140,50 @@ func (g *GstBaseSrc) SetFormat(format gst.Format) { C.gst_base_src_set_format(g.Instance(), C.GstFormat(format)) } +// SetLive sets if the element listens to a live source. +// +// A live source will not produce data in the PAUSED state and will therefore not be able to participate in the +// PREROLL phase of a pipeline. To signal this fact to the application and the pipeline, the state change return +// value of the live source will be gst.StateChangeNoPreroll. +func (g *GstBaseSrc) SetLive(live bool) { C.gst_base_src_set_live(g.Instance(), gboolean(live)) } + // StartComplete completes an asynchronous start operation. When the subclass overrides the start method, // it should call StartComplete when the start operation completes either from the same thread or from an // asynchronous helper thread. func (g *GstBaseSrc) StartComplete(ret gst.FlowReturn) { C.gst_base_src_start_complete(g.Instance(), C.GstFlowReturn(ret)) } + +// StartWait waits until the start operation is complete. +func (g *GstBaseSrc) StartWait() gst.FlowReturn { + return gst.FlowReturn(C.gst_base_src_start_wait(g.Instance())) +} + +// SubmitBufferList submits a list of buffers to the source. +// +// Subclasses can call this from their create virtual method implementation to submit a buffer list to be pushed out +// later. This is useful in cases where the create function wants to produce multiple buffers to be pushed out in one +// go in form of a GstBufferList, which can reduce overhead drastically, especially for packetised inputs (for data +// streams where the packetisation/chunking is not important it is usually more efficient to return larger buffers instead). +// +// Subclasses that use this function from their create function must return GST_FLOW_OK and no buffer from their create +// virtual method implementation. If a buffer is returned after a buffer list has also been submitted via this function the +// behaviour is undefined. +// +// Subclasses must only call this function once per create function call and subclasses must only call this function when +// the source operates in push mode. +func (g *GstBaseSrc) SubmitBufferList(bufferList *gst.BufferList) { + C.gst_base_src_submit_buffer_list(g.Instance(), (*C.GstBufferList)(unsafe.Pointer(bufferList.Instance()))) +} + +// WaitPlaying will block until a state change to PLAYING happens (in which case this function returns gst.FlowOK) or the +// processing must be stopped due to a state change to READY or a FLUSH event (in which case this function returns GST_FLOW_FLUSHING). +// +// If the Create() method performs its own synchronisation against the clock it must unblock when going from PLAYING to the PAUSED +// state and call this method before continuing to produce the remaining data. +// +// gst.FlowOK will be returned if the source is PLAYING and processing can continue. Any other return value should be +// returned from the Create() vmethod. +func (g *GstBaseSrc) WaitPlaying() gst.FlowReturn { + return gst.FlowReturn(C.gst_base_src_wait_playing(g.Instance())) +} diff --git a/gst/base/util.go b/gst/base/util.go index 21edef5..891e18e 100644 --- a/gst/base/util.go +++ b/gst/base/util.go @@ -10,3 +10,8 @@ func gboolean(b bool) C.gboolean { } return C.gboolean(0) } + +// gobool provides an easy type conversion between C.gboolean and a go bool. +func gobool(b C.gboolean) bool { + return int(b) > 0 +} diff --git a/gst/gst_allocator.go b/gst/gst_allocator.go index 9fe701d..c5e624d 100644 --- a/gst/gst_allocator.go +++ b/gst/gst_allocator.go @@ -14,6 +14,11 @@ type AllocationParams struct { ptr *C.GstAllocationParams } +// FromGstAllocationParamsUnsafe wraps the given unsafe.Pointer in an AllocationParams instance. +func FromGstAllocationParamsUnsafe(alloc unsafe.Pointer) *AllocationParams { + return &AllocationParams{ptr: (*C.GstAllocationParams)(alloc)} +} + // NewAllocationParams initializes a set of allocation params with the default // values. func NewAllocationParams() *AllocationParams { @@ -65,6 +70,11 @@ func (a *AllocationParams) SetPadding(padding int64) { a.ptr.padding = C.gsize(p // Allocator is a go representation of a GstAllocator type Allocator struct{ *Object } +// FromGstAllocatorUnsafe wraps the given unsafe.Pointer in an Allocator instance. +func FromGstAllocatorUnsafe(alloc unsafe.Pointer) *Allocator { + return wrapAllocator(toGObject(alloc)) +} + // DefaultAllocator returns the default GstAllocator. func DefaultAllocator() *Allocator { return wrapAllocator(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(C.gst_allocator_find(nil)))}) diff --git a/gst/gst_buffer_list.go b/gst/gst_buffer_list.go index cb41be4..d2c15f0 100644 --- a/gst/gst_buffer_list.go +++ b/gst/gst_buffer_list.go @@ -22,9 +22,17 @@ type BufferList struct { ptr *C.GstBufferList } -// NewBufferList returns a new empty BufferList. -func NewBufferList() *BufferList { - return wrapBufferList(C.gst_buffer_list_new()) +// NewBufferList returns a new BufferList. The given slice can be nil and the returned +// buffer list will be empty. +func NewBufferList(buffers []*Buffer) *BufferList { + bufList := wrapBufferList(C.gst_buffer_list_new()) + if buffers == nil { + return bufList + } + for idx, buf := range buffers { + bufList.Insert(idx, buf) + } + return bufList } // NewBufferListSized creates a new BufferList with the given size. @@ -32,6 +40,12 @@ func NewBufferListSized(size uint) *BufferList { return wrapBufferList(C.gst_buffer_list_new_sized(C.guint(size))) } +// FromGstBufferListUnsafe wraps the given unsafe.Pointer in a BufferList instance. It is meant for internal usage +// and exported for visibility to other packages. +func FromGstBufferListUnsafe(ptr unsafe.Pointer) *BufferList { + return wrapBufferList((*C.GstBufferList)(ptr)) +} + // Instance returns the underlying GstBufferList. func (b *BufferList) Instance() *C.GstBufferList { return C.toGstBufferList(unsafe.Pointer(b.ptr)) } diff --git a/gst/gst_buffer_pool.go b/gst/gst_buffer_pool.go index 19df1f6..d86b896 100644 --- a/gst/gst_buffer_pool.go +++ b/gst/gst_buffer_pool.go @@ -22,6 +22,11 @@ func NewBufferPool() *BufferPool { return wrapBufferPool(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(pool))}) } +// FromGstBufferPoolUnsafe wraps the given unsafe.Pointer in a BufferPool instance. +func FromGstBufferPoolUnsafe(bufferPool unsafe.Pointer) *BufferPool { + return wrapBufferPool(toGObject(bufferPool)) +} + // Instance returns the underlying GstBufferPool instance. func (b *BufferPool) Instance() *C.GstBufferPool { return C.toGstBufferPool(b.Unsafe()) } diff --git a/gst/gst_bus.go b/gst/gst_bus.go index 0ac0f71..f2683a0 100644 --- a/gst/gst_bus.go +++ b/gst/gst_bus.go @@ -21,7 +21,6 @@ import "C" import ( "errors" - "fmt" "reflect" "runtime/debug" "sync" @@ -327,7 +326,7 @@ func (b *Bus) PostError(src interface{}, msg string, err error) bool { if err != nil { st = NewStructure("go-error") if addErr := st.SetValue("error", err.Error()); addErr != nil { - fmt.Println("go-gst-warning: failed to set error message to structure") + b.Log(CAT, LevelWarning, "failed to set error message to structure") } } gstMsg := NewErrorMessage(src, gerr, string(debug.Stack()), st) diff --git a/gst/gst_sample.go b/gst/gst_sample.go index 3a101d2..3765a7a 100644 --- a/gst/gst_sample.go +++ b/gst/gst_sample.go @@ -17,10 +17,55 @@ func FromGstSampleUnsafe(sample unsafe.Pointer) *Sample { return wrapSample(C.to // Instance returns the underlying *GstSample instance. func (s *Sample) Instance() *C.GstSample { return C.toGstSample(unsafe.Pointer(s.sample)) } -// Unref calls gst_sample_unref on the sample. -func (s *Sample) Unref() { C.gst_sample_unref((*C.GstSample)(s.Instance())) } +// Ref increases the ref count on the sample. +func (s *Sample) Ref() *Sample { + return wrapSample(C.gst_sample_ref(s.Instance())) +} + +// Copy creates a copy of the given sample. This will also make a newly allocated copy of the data +// the source sample contains. +func (s *Sample) Copy() *Sample { return wrapSample(C.gst_sample_copy(s.Instance())) } // GetBuffer returns the buffer inside this sample. func (s *Sample) GetBuffer() *Buffer { return wrapBuffer(C.gst_sample_get_buffer((*C.GstSample)(s.Instance()))) } + +// GetBufferList gets the buffer list associated with this sample. +func (s *Sample) GetBufferList() *BufferList { + return wrapBufferList(C.gst_sample_get_buffer_list(s.Instance())) +} + +// GetCaps returns the caps associated with this sample. Take a ref if you need to hold on to them +// longer then the life of the sample. +func (s *Sample) GetCaps() *Caps { return wrapCaps(C.gst_sample_get_caps(s.Instance())) } + +// GetInfo gets extra information about this sample. The structure remains valid as long as sample is valid. +func (s *Sample) GetInfo() *Structure { return wrapStructure(C.gst_sample_get_info(s.Instance())) } + +// GetSegment gets the segment associated with the sample. The segmenr remains valid as long as sample is valid. +func (s *Sample) GetSegment() *Segment { return wrapSegment(C.gst_sample_get_segment(s.Instance())) } + +// SetBuffer sets the buffer inside this sample. The sample must be writable. +func (s *Sample) SetBuffer(buf *Buffer) { C.gst_sample_set_buffer(s.Instance(), buf.Instance()) } + +// SetBufferList sets the buffer list for this sample. The sample must be writable. +func (s *Sample) SetBufferList(buf *BufferList) { + C.gst_sample_set_buffer_list(s.Instance(), buf.Instance()) +} + +// SetCaps sets the caps on this sample. The sample must be writable. +func (s *Sample) SetCaps(caps *Caps) { C.gst_sample_set_caps(s.Instance(), caps.Instance()) } + +// SetInfo sets the info on this sample. The sample must be writable. +func (s *Sample) SetInfo(st *Structure) bool { + return gobool(C.gst_sample_set_info(s.Instance(), st.Instance())) +} + +// SetSegment sets the segment on this sample. The sample must be writable. +func (s *Sample) SetSegment(segment *Segment) { + C.gst_sample_set_segment(s.Instance(), segment.Instance()) +} + +// Unref calls gst_sample_unref on the sample. +func (s *Sample) Unref() { C.gst_sample_unref((*C.GstSample)(s.Instance())) } diff --git a/gst/gst_structure.go b/gst/gst_structure.go index a9b5d22..0d87930 100644 --- a/gst/gst_structure.go +++ b/gst/gst_structure.go @@ -75,6 +75,12 @@ func MarshalStructure(data interface{}) *Structure { return st } +// FromGstStructureUnsafe wraps the given unsafe.Pointer in a Structure. This is meant for internal usage +// and is exported for visibility to other packages. +func FromGstStructureUnsafe(st unsafe.Pointer) *Structure { + return wrapStructure((*C.GstStructure)(st)) +} + // UnmarshalInto will unmarshal this structure into the given pointer. The object // reflected by the pointer must be non-nil. func (s *Structure) UnmarshalInto(data interface{}) error { diff --git a/gst/gstauto/util.go b/gst/gstauto/util.go index 0793fb3..8f2094c 100644 --- a/gst/gstauto/util.go +++ b/gst/gstauto/util.go @@ -1,9 +1,9 @@ package gstauto -import "fmt" +import "github.com/tinyzimmer/go-gst/gst" func runOrPrintErr(f func() error) { if err := f(); err != nil { - fmt.Println("[go-gst/gst/gstauto] Internal Error:", err.Error()) + gst.CAT.Log(gst.LevelError, err.Error()) } }