From 26cf65f211969f4f99cf4ee108677055cb09a9e0 Mon Sep 17 00:00:00 2001 From: Avi Zimmerman Date: Wed, 6 Jan 2021 22:50:03 +0200 Subject: [PATCH] Merge plugin experimentation branch - GstBaseSrcs can now be implemented via the bindings but with very limited functionality still --- examples/inspect/colors.go | 89 ----- examples/inspect/main.go | 304 --------------- examples/inspect/print_object_properties.go | 349 ------------------ examples/plugins/filesrc/filesrc.go | 325 ++++++++++++++++ examples/plugins/filesrc/plugin.go | 46 +++ go.sum | 1 + gst/app/gst.go.h | 5 + gst/base/doc.go | 2 + gst/base/gst.go.h | 9 + gst/base/gst_base_src.go | 47 +++ gst/base/gst_base_src_exports.go | 198 ++++++++++ gst/base/gst_base_src_impl.go | 236 ++++++++++++ gst/base/pkg_config.go | 7 + gst/base/util.go | 12 + gst/c_util.go | 10 + gst/cgo_exports.go | 118 +++++- gst/constants.go | 62 +++- gst/g_error.go | 10 +- gst/g_object_class.go | 75 ++++ ..._parameter_spec.go => g_parameter_spec.go} | 76 +++- gst/gst.go.h | 5 + gst/gst_buffer.go | 6 + gst/gst_element.go | 324 +++++++++------- gst/gst_element_class.go | 102 +++++ gst/gst_errors.go | 84 +++++ gst/gst_event.go | 4 + gst/gst_object.go | 7 +- gst/gst_plugin.go | 155 +++++++- gst/gst_query.go | 4 + gst/gst_segment.go | 7 + gst/gst_uri_handler.go | 17 +- gst/gst_wrappers.go | 4 + gst/interfaces.go | 158 ++++++++ gst/register.go | 11 + 34 files changed, 1950 insertions(+), 919 deletions(-) delete mode 100644 examples/inspect/colors.go delete mode 100644 examples/inspect/main.go delete mode 100644 examples/inspect/print_object_properties.go create mode 100644 examples/plugins/filesrc/filesrc.go create mode 100644 examples/plugins/filesrc/plugin.go create mode 100644 gst/base/doc.go create mode 100644 gst/base/gst.go.h create mode 100644 gst/base/gst_base_src.go create mode 100644 gst/base/gst_base_src_exports.go create mode 100644 gst/base/gst_base_src_impl.go create mode 100644 gst/base/pkg_config.go create mode 100644 gst/base/util.go create mode 100644 gst/g_object_class.go rename gst/{gst_parameter_spec.go => g_parameter_spec.go} (79%) create mode 100644 gst/gst_element_class.go create mode 100644 gst/gst_errors.go create mode 100644 gst/interfaces.go create mode 100644 gst/register.go diff --git a/examples/inspect/colors.go b/examples/inspect/colors.go deleted file mode 100644 index 8201568..0000000 --- a/examples/inspect/colors.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "regexp" - "strings" - "text/tabwriter" -) - -type color string - -var ( - colorReset color = "\033[0m" - colorBlack color = "\033[0;30m" - colorRed color = "\033[0;31m" - colorGreen color = "\033[0;32m" - colorOrange color = "\033[0;33m" - colorBlue color = "\033[0;34m" - colorPurple color = "\033[0;35m" - colorCyan color = "\033[0;36m" - colorLightGray color = "\033[0;37m" - colorDarkGray color = "\033[1;30m" - colorLightRed color = "\033[1;31m" - colorLightGreen color = "\033[1;32m" - colorYellow color = "\033[1;33m" - colorLightBlue color = "\033[1;34m" - colorLightPurple color = "\033[1;35m" - colorLightCyan color = "\033[1;36m" - colorWhite color = "\033[1;37m" -) - -func disableColor() { - colorReset = "" - colorBlack = "" - colorRed = "" - colorGreen = "" - colorOrange = "" - colorBlue = "" - colorPurple = "" - colorCyan = "" - colorLightGray = "" - colorDarkGray = "" - colorLightRed = "" - colorLightGreen = "" - colorYellow = "" - colorLightBlue = "" - colorLightPurple = "" - colorLightCyan = "" - colorWhite = "" -} - -func (c color) print(s string) { fmt.Printf("%s%s%s", c, s, colorReset) } -func (c color) sprint(s string) string { return fmt.Sprintf("%s%s%s", c, s, colorReset) } -func (c color) printIndent(i int, s string) { c.print(fmt.Sprintf("%s%s", strings.Repeat(" ", i), s)) } -func (c color) printf(f string, args ...interface{}) { c.print(fmt.Sprintf(f, args...)) } -func (c color) printfIndent(i int, f string, args ...interface{}) { - c.printf(fmt.Sprintf("%s%s", strings.Repeat(" ", i), fmt.Sprintf(f, args...))) -} -func (c color) fprint(w io.Writer, s string) { fmt.Fprintf(w, fmt.Sprintf("%s%s%s", c, s, colorReset)) } -func (c color) fprintf(w io.Writer, f string, args ...interface{}) { - c.fprint(w, fmt.Sprintf(f, args...)) -} -func (c color) fprintIndent(w io.Writer, i int, s string) { - c.fprint(w, fmt.Sprintf("%s%s", strings.Repeat(" ", i), s)) -} -func (c color) fprintfIndent(w io.Writer, i int, f string, args ...interface{}) { - c.fprint(w, fmt.Sprintf("%s%s", strings.Repeat(" ", i), fmt.Sprintf(f, args...))) -} - -var srcRegex = regexp.MustCompile("^\\[.*?\\]") -var typeRegex = regexp.MustCompile("[A-Z\\-]+") -var msgRegex = regexp.MustCompile("[^\\-]+$") - -// colorify adds color to a string in the format of "[src] TYPE - MESSAGE" -func colorify(str string) string { - buf := new(bytes.Buffer) - w := new(tabwriter.Writer) - w.Init(buf, 4, 24, 0, '\t', 0) - fmt.Fprintf(w, - "%s \t %s \t %s", - colorBlue.sprint(srcRegex.FindString(str)), - colorGreen.sprint(typeRegex.FindString(str)), - colorLightGray.sprint(msgRegex.FindString(str)), - ) - w.Flush() - return buf.String() -} diff --git a/examples/inspect/main.go b/examples/inspect/main.go deleted file mode 100644 index 1d5938c..0000000 --- a/examples/inspect/main.go +++ /dev/null @@ -1,304 +0,0 @@ -// Inspect is a simplified version of gst-inspect-. It parses -// the name of a plugin on the command line and dumps the element's properties -// to stdout. -package main - -import ( - "bytes" - "fmt" - "os" - "strings" - "text/tabwriter" - - "github.com/gotk3/gotk3/glib" - "github.com/tinyzimmer/go-gst/gst" -) - -func main() { - if len(os.Args) == 1 { - fmt.Println("You must provide an element to inspect") - os.Exit(1) - } - - gst.Init(nil) - - if err := inspect(os.Args[1]); err != nil { - fmt.Println(err) - os.Exit(2) - } -} - -func inspect(name string) error { - // load the registry - registry := gst.GetRegistry() - // get the factory for the element - factory := gst.Find(name) - - if factory == nil { - return fmt.Errorf("Could not get details for factory '%s'", name) - } - defer factory.Unref() - - // assume it's an element for now, can implement more later - elem, err := gst.NewElement(name) - if err != nil { - return err - } - defer elem.Unref() - - var maxLevel int - - // dump info about the element - - printFactoryDetails(registry, factory) - printPluginDetails(registry, factory) - printHierarchy(elem.TypeFromInstance(), 0, &maxLevel) - printInterfaces(elem) - printPadTemplates(elem) - printClockingInfo(elem) - printURIHandlerInfo(elem) - printPadInfo(elem) - printElementPropertiesInfo(elem) - printSignalInfo(elem) - printChildrenInfo(elem) - printPresentList(elem) - - return nil -} - -func printFactoryDetails(registry *gst.Registry, factory *gst.ElementFactory) { - - // initialize tabwriter - w := new(tabwriter.Writer) - buf := new(bytes.Buffer) - - w.Init( - buf, - 40, // minwidth - 30, // tabwith - 0, // padding - ' ', // padchar - 0, // flags - ) - - colorOrange.fprint(w, "Factory Details:\n") - for _, key := range factory.GetMetadataKeys() { - colorBlue.fprintfIndent(w, 2, "%s \t ", strings.Title(key)) - colorLightGray.fprint(w, factory.GetMetadata(key)) - colorReset.fprint(w, "\n") - } - - w.Flush() - fmt.Print(buf.String()) - fmt.Println() -} - -func printPluginDetails(registry *gst.Registry, factory *gst.ElementFactory) { - - // initialize tabwriter - w := new(tabwriter.Writer) - buf := new(bytes.Buffer) - - w.Init( - buf, - 40, // minwidth - 30, // tabwith - 0, // padding - ' ', // padchar - 0, // flags - ) - - pluginFeature, err := registry.LookupFeature(factory.Name()) - if err != nil { - return - } - plugin := pluginFeature.GetPlugin() - if plugin == nil { - return - } - - defer pluginFeature.Unref() - defer plugin.Unref() - - colorOrange.fprint(w, "Plugin Details:\n") - - colorBlue.fprintIndent(w, 2, "Name \t ") - colorLightGray.fprintf(w, "%s\n", pluginFeature.GetPluginName()) - - colorBlue.fprintIndent(w, 2, "Description \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Description()) - - colorBlue.fprintIndent(w, 2, "Filename \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Filename()) - - colorBlue.fprintIndent(w, 2, "Version \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Version()) - - colorBlue.fprintIndent(w, 2, "License \t ") - colorLightGray.fprintf(w, "%s\n", plugin.License()) - - colorBlue.fprintIndent(w, 2, "Source module \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Source()) - - colorBlue.fprintIndent(w, 2, "Binary package \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Package()) - - colorBlue.fprintIndent(w, 2, "Origin URLs \t ") - colorLightGray.fprintf(w, "%s\n", plugin.Origin()) - - w.Flush() - fmt.Print(buf.String()) - - fmt.Println() -} - -func printHierarchy(gtype glib.Type, level int, maxLevel *int) { - parent := gtype.Parent() - - *maxLevel = *maxLevel + 1 - level++ - - if parent > 0 { - printHierarchy(parent, level, maxLevel) - } - - for i := 1; i < *maxLevel-level; i++ { - colorReset.print(" ") - } - - if *maxLevel-level > 0 { - colorLightPurple.print(" +----") - } - - colorGreen.printf("%s\n", gtype.Name()) - -} - -func printInterfaces(elem *gst.Element) { - fmt.Println() - - if ifaces := elem.Interfaces(); len(ifaces) > 0 { - colorOrange.print("Implemented Interfaces:") - for _, iface := range ifaces { - colorGreen.printfIndent(2, "%s\n", iface) - } - } -} - -func printPadTemplates(elem *gst.Element) { - fmt.Println() - - tmpls := elem.GetPadTemplates() - if len(tmpls) == 0 { - return - } - colorOrange.print("Pad templates:\n") - for _, tmpl := range tmpls { - colorBlue.printfIndent(2, "%s template", strings.ToUpper(tmpl.Name())) - colorReset.print(": ") - colorBlue.printf("'%s'\n", strings.ToLower(tmpl.Direction().String())) - - colorBlue.printIndent(4, "Availability") - colorReset.print(": ") - colorLightGray.print(strings.Title(tmpl.Presence().String())) - colorReset.print("\n") - - colorBlue.printIndent(4, "Capabilities") - colorReset.print(": ") - - printCaps(tmpl.Caps(), 6) - } - fmt.Println() - fmt.Println() -} - -func printClockingInfo(elem *gst.Element) { - if !elem.Has(gst.ElementFlagRequireClock) && !elem.Has(gst.ElementFlagProvideClock) { - colorLightGray.print("Element has no clocking capabilities.\n") - return - } - fmt.Printf("%sClocking Interactions:%s\n", colorOrange, colorReset) - - if elem.Has(gst.ElementFlagRequireClock) { - colorLightGray.printIndent(2, "element requires a clock\n") - } - - if elem.Has(gst.ElementFlagProvideClock) { - clock := elem.GetClock() - if clock == nil { - colorLightGray.printIndent(2, "element is supposed to provide a clock but returned NULL%s\n") - } else { - defer clock.Unref() - colorLightGray.printIndent(2, "element provides a clock: ") - colorCyan.printf(clock.Name()) - } - } - - fmt.Println() -} - -func printURIHandlerInfo(elem *gst.Element) { - if !elem.IsURIHandler() { - colorLightGray.print("Element has no URI handling capabilities.\n") - fmt.Println() - } - - uriHandler := elem.URIHandler() - - colorOrange.print("URI handling capabilities:\n") - colorLightGray.printfIndent(2, "Element can act as %s.\n", strings.ToLower(uriHandler.GetURIType().String())) - - protos := uriHandler.GetURIProtocols() - - if len(protos) == 0 { - fmt.Println() - return - } - - colorLightGray.printIndent(2, "Supported URI protocols:\n") - - for _, proto := range protos { - colorCyan.printfIndent(4, "%s\n", proto) - } - - fmt.Println() -} - -func printPadInfo(elem *gst.Element) { - - colorOrange.print("Pads:\n") - pads := elem.GetPads() - if len(pads) == 0 { - colorCyan.printIndent(2, "none\n") - return - } - for _, pad := range elem.GetPads() { - defer pad.Unref() - - colorBlue.printIndent(2, strings.ToUpper(pad.Direction().String())) - colorReset.print(": ") - colorLightGray.printf("'%s'\n", pad.Name()) - - if tmpl := pad.Template(); tmpl != nil { - defer tmpl.Unref() - colorBlue.printIndent(4, "Pad Template") - colorReset.print(": ") - colorLightGray.printf("'%s'\n", tmpl.Name()) - } - - if caps := pad.CurrentCaps(); caps != nil { - colorBlue.printIndent(2, "Capabilities:\n") - printCaps(caps, 4) - } - } - - fmt.Println() -} - -func printElementPropertiesInfo(elem *gst.Element) { - printObjectPropertiesInfo(elem.Object, "Element Properties") -} - -func printSignalInfo(elem *gst.Element) {} -func printChildrenInfo(elem *gst.Element) {} -func printPresentList(elem *gst.Element) {} diff --git a/examples/inspect/print_object_properties.go b/examples/inspect/print_object_properties.go deleted file mode 100644 index ed5c1e9..0000000 --- a/examples/inspect/print_object_properties.go +++ /dev/null @@ -1,349 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "sort" - "strconv" - "text/tabwriter" - - "github.com/gotk3/gotk3/glib" - "github.com/tinyzimmer/go-gst/gst" -) - -func printFieldType(s string) { - colorGreen.printIndent(24, s) -} - -func printFieldName(s string) { - colorOrange.print(s) - colorReset.print(": ") -} - -func printFieldValue(s string) { - colorCyan.printf("%s ", s) -} - -// ByName implements sort. Interface based on the Name field. -type ByName []*gst.ParameterSpec - -func (a ByName) Len() int { return len(a) } -func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// ByValue implements sort. Interface based on the Value field. -type ByValue []*gst.FlagsValue - -func (a ByValue) Len() int { return len(a) } -func (a ByValue) Less(i, j int) bool { return a[i].Value < a[j].Value } -func (a ByValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -func printObjectPropertiesInfo(obj *gst.Object, description string) { - colorOrange.printf("%s:\n", description) - - // for now this function only handles elements - - props := obj.ListProperties() - sort.Sort(ByName(props)) - - for _, param := range props { - defer param.Unref() - - colorBlue.printfIndent(2, "%-20s", param.Name) - colorReset.printf(": %s", param.Blurb) - - colorReset.print("\n") - - colorOrange.printIndent(24, "flags") - colorReset.print(": ") - colorCyan.print(param.Flags.GstFlagsString()) - - if !param.Flags.Has(gst.ParameterReadable) { - colorReset.print(" | ") - colorLightPurple.print("Write only") - } else if !param.Flags.Has(gst.ParameterWritable) { - colorReset.print(" | ") - colorLightPurple.print("Read only") - } - - colorReset.print("\n") - - // get the value and continue on any error (very unlikely) - - goval, _ := param.DefaultValue.GoValue() - - // skips deprecated types - - switch param.ValueType { - - case glib.TYPE_STRING: - printFieldType("String. ") - printFieldName("Default") - if goval == nil { - printFieldValue("null") - } else { - str, _ := param.DefaultValue.GetString() - printFieldValue(str) - } - - case glib.TYPE_BOOLEAN: - var valStr string - if goval == nil { - valStr = "unknown" // edge case - } else { - b := goval.(bool) - valStr = fmt.Sprintf("%t", b) - } - printFieldType("Boolean. ") - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_UINT: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(uint) - valStr = fmt.Sprintf("%d", v) - } - printFieldType("Unsigned Integer. ") - printFieldName("Range") - min, max := param.UIntRange() - printFieldValue(fmt.Sprintf("%d - %d ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_INT: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(int) - valStr = fmt.Sprintf("%d", v) - } - printFieldType("Integer. ") - printFieldName("Range") - min, max := param.IntRange() - printFieldValue(fmt.Sprintf("%d - %d ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_UINT64: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(uint64) - valStr = fmt.Sprintf("%d", v) - } - printFieldType("Unsigned Integer64. ") - printFieldName("Range") - min, max := param.UInt64Range() - printFieldValue(fmt.Sprintf("%d - %d ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_INT64: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(int64) - valStr = fmt.Sprintf("%d", v) - } - printFieldType("Integer64. ") - printFieldName("Range") - min, max := param.Int64Range() - printFieldValue(fmt.Sprintf("%d - %d ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_FLOAT: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(float64) - valStr = fmt.Sprintf("%15.7g", v) - } - printFieldType("Float. ") - printFieldName("Range") - min, max := param.FloatRange() - printFieldValue(fmt.Sprintf("%15.7g - %15.7g ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - case glib.TYPE_DOUBLE: - var valStr string - if goval == nil { - valStr = "0" - } else { - v := goval.(float64) - valStr = fmt.Sprintf("%15.7g", v) - } - printFieldType("Double. ") - printFieldName("Range") - min, max := param.DoubleRange() - printFieldValue(fmt.Sprintf("%15.7g - %15.7g ", min, max)) - printFieldName("Default") - printFieldValue(valStr) - - default: - if param.IsCaps() { - - if caps := param.GetCaps(); caps != nil { - printCaps(caps, 24) - } - - } else if param.IsEnum() { - - enumValues := param.GetEnumValues() - iface, _ := param.DefaultValue.GoValue() - var curVal string - if iface == nil { - curVal = "-1" - } else { - curVal = fmt.Sprintf("%v", iface) - } - var defaultStr string - for _, val := range enumValues { - if curVal == strconv.Itoa(val.Value) { - defaultStr = val.ValueNick - } - } - printFieldType(fmt.Sprintf(`Enum "%s" `, param.ValueType.Name())) - printFieldName("Default") - printFieldValue(fmt.Sprintf(`%s "%s"`, curVal, defaultStr)) - colorReset.print("\n") - for idx, val := range enumValues { - w := new(tabwriter.Writer) - buf := new(bytes.Buffer) - w.Init(buf, 100, 73, 0, '\t', 0) - colorOrange.fprintfIndent(w, 27, "(%d)", val.Value) - colorReset.fprint(w, ": ") - colorCyan.fprint(w, val.ValueNick) - colorReset.fprint(w, "\t - ") - colorLightGray.fprint(w, val.ValueName) - w.Flush() - fmt.Print(buf.String()) - if idx < len(enumValues)-1 { - colorReset.print("\n") - } - } - - } else if param.IsFlags() { - - flags := param.GetFlagValues() - sort.Sort(ByValue(flags)) - flagStr := "+" - for _, flag := range flags { - flagStr += fmt.Sprintf(" %s", flag.ValueNick) - } - if flagStr == "+" { - flagStr = "(none)" - } - printFieldType(fmt.Sprintf(`Flags "%s" `, param.ValueType.Name())) - printFieldName("Default") - printFieldValue(fmt.Sprintf(`0x%08x "%s"`, param.GetDefaultFlags(), flagStr)) - - for idx, flag := range flags { - w := new(tabwriter.Writer) - buf := new(bytes.Buffer) - w.Init(buf, 100, 73, 0, '\t', 0) - colorOrange.fprintfIndent(w, 27, "(%d)", flag.Value) - colorReset.fprint(w, ": ") - colorCyan.fprint(w, flag.ValueNick) - colorReset.fprint(w, "\t - ") - colorLightGray.fprint(w, flag.ValueName) - w.Flush() - fmt.Print(buf.String()) - if idx < len(flags)-1 { - colorReset.print("\n") - } - } - - } else if param.IsObject() { - - colorLightGray.printIndent(24, "Object of type ") - colorGreen.printf(`"%s"`, param.ValueType.Name()) - - } else if param.IsBoxed() { - - colorLightGray.printIndent(24, "Boxed pointer of type ") - colorGreen.printf(`"%s"`, param.ValueType.Name()) - if param.ValueType.Name() == "GstStructure" { - structure := gst.StructureFromGValue(param.DefaultValue) - if structure != nil { - for key, val := range structure.Values() { - colorReset.printIndent(26, "(gpointer) ") - colorYellow.printf("%15s:", key) - colorBlue.printf("%v", val) - } - } - } - - } else if param.IsPointer() { - - colorLightGray.printIndent(24, "Pointer of type ") - colorGreen.printf(`"%s`, param.ValueType.Name()) - - } else if param.IsFraction() { - - colorGreen.printIndent(24, "Fraction.") - - } else if param.IsGstArray() { - - colorGreen.printIndent(24, "GstArray.") - - } else { - colorReset.printIndent(24, "Unknown type ") - colorGreen.printf(`"%s`, param.ValueType.Name()) - } - } - - colorReset.print("\n") - - } - - fmt.Println() -} - -func printCaps(caps *gst.Caps, indent int) { - if caps == nil { - return - } - - colorReset.print("\n") - defer func() { colorReset.print("\n") }() - - if caps.IsAny() { - colorOrange.printIndent(indent, "ANY") - return - } - - if caps.IsEmpty() { - colorOrange.printIndent(indent, "EMPTY") - return - } - - for i := 0; i < caps.GetSize(); i++ { - structure := caps.GetStructureAt(i) - features := caps.GetFeaturesAt(i) - - if features != nil && features.IsAny() { - colorOrange.printIndent(indent+20, structure.Name()) - colorLightGray.print(" (") - colorGreen.print(features.String()) - colorLightGray.print(")") - } else { - colorOrange.printIndent(indent+20, structure.Name()) - } - - colorReset.print("\n") - for k, v := range structure.Values() { - colorCyan.printIndent(indent+10, k) - colorReset.print(": ") - colorBlue.printf("%v\n", v) - } - } -} diff --git a/examples/plugins/filesrc/filesrc.go b/examples/plugins/filesrc/filesrc.go new file mode 100644 index 0000000..7a005da --- /dev/null +++ b/examples/plugins/filesrc/filesrc.go @@ -0,0 +1,325 @@ +// This example demonstrates a filesrc plugin implemented in Go. +// +// Every element in a Gstreamer pipeline is provided by plugins. Some are builtin while +// others are provided by third-parties or distributed privately. The plugins are built +// around the GObject type system. +// +// Go-gst offers loose bindings around the GObject type system to provide the necessary +// functionality to implement these plugins. The example in this code produces an element +// that can read from a file on the local system. +// +// In order to build the plugin for use by GStreamer, you may do the following: +// +// $ go build -o libgstgofilesrc.so -buildmode c-shared . +// +// +package main + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + "syscall" + + "github.com/gotk3/gotk3/glib" + "github.com/tinyzimmer/go-gst/gst" + "github.com/tinyzimmer/go-gst/gst/base" +) + +// 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 read from. +// 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 of the file to read from", // 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 reading from + 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 fileSrc struct { + // The settings for the element + settings *settings + // The current state of the element + state *state +} + +// 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 +// local value with it. +func (f *fileSrc) setLocation(path string) error { + if f.state.started { + return errors.New("Changing the `location` property on a started `GoFileSrc` is not supported") + } + path = strings.TrimPrefix(path, "file://") + if path == "" { + f.settings.location = "" + return nil + } + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s does not exist", path) + } + return fmt.Errorf("Could not stat %s, err: %s", path, err.Error()) + } + if stat.IsDir() { + return fmt.Errorf("%s is a directory", path) + } + f.settings.location = path + return nil +} + +// The ObjectSubclass implementations below are for registering the various aspects of our +// element and its capabilities with the type system. + +// Every element needs to provide its own constructor that returns the an initialized +// gst.GoElement implementation. Here we simply create a new fileSrc with zeroed settings +// and state objects. +func (f *fileSrc) New() gst.GoElement { + return &fileSrc{ + 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 *fileSrc) TypeInit(instance *gst.TypeInstance) { + instance.AddInterface(gst.InterfaceURIHandler) +} + +// The ClassInit method should specify the metadata for this element as well as add any pad templates +// and properties. +func (f *fileSrc) ClassInit(klass *gst.ElementClass) { + klass.SetMetadata( + "File Source", + "Source/File", + "Read stream from a file", + "Avi Zimmerman ", + ) + caps := gst.NewAnyCaps() + srcPadTemplate := gst.NewPadTemplate( + "src", + gst.PadDirectionSource, + gst.PadPresenceAlways, + caps, + ) + klass.AddPadTemplate(srcPadTemplate) + klass.InstallProperties(properties) +} + +// Object implementations are used during the initialization of element. The +// methods are called once the obejct is constructed and its properties are read +// and written to. + +// 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 *fileSrc) 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).Error(gst.DomainLibrary, gst.LibraryErrorSettings, + "Could not set location on object", + err.Error(), + ) + } + gst.ToElement(self).Info(gst.DomainLibrary, 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 *fileSrc) 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).Error(gst.DomainLibrary, gst.LibraryErrorSettings, + fmt.Sprintf("Could not convert %s to GValue", f.settings.location), + err.Error(), + ) + } + return nil +} + +// Constructed is called when the type system is done constructing the object. Any finalizations required +// during the initializatin cycle can be performed here. In this example, we set the format on our +// underlying GstBaseSrc to bytes. +func (f *fileSrc) Constructed(self *gst.Object) { + base.ToGstBaseSrc(self).SetFormat(gst.FormatBytes) +} + +// GstBaseSrc implementations are optional methods to implement from the base.GstBaseSrcImpl interface. +// If the method is not overridden by the implementing struct, it will be inherited from the parent class. + +// IsSeekable returns that we are, in fact, seekable. +func (f *fileSrc) IsSeekable(*base.GstBaseSrc) bool { return true } + +// GetSize will return the total size of the file at the configured location. +func (f *fileSrc) GetSize(self *base.GstBaseSrc) (bool, int64) { + if !f.state.started { + return false, 0 + } + stat, err := f.state.file.Stat() + if err != nil { + // This should never happen + self.Error(gst.DomainResource, gst.ResourceErrorFailed, + "Could not retrieve fileinfo on opened file", + err.Error(), + ) + return false, 0 + } + return true, stat.Size() +} + +// Start is called to start this element. In this example, the configured file is opened for reading, +// and any error encountered in the process is posted to the pipeline. +func (f *fileSrc) Start(self *base.GstBaseSrc) bool { + if f.state.started { + self.Error(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is already started", "") + return false + } + + if f.settings.location == "" { + self.Error(gst.DomainResource, gst.ResourceErrorSettings, "File location is not defined", "") + return false + } + + var err error + f.state.file, err = os.OpenFile(f.settings.location, syscall.O_RDONLY, 0444) + if err != nil { + self.Error(gst.DomainResource, gst.ResourceErrorOpenRead, + fmt.Sprintf("Could not open file %s for reading", f.settings.location), err.Error()) + return false + } + f.state.position = 0 + + f.state.started = true + + self.StartComplete(gst.FlowOK) + + self.Info(gst.DomainResource, "Started") + return true +} + +// Stop is called to stop the element. The file is closed and the local values are zeroed out. +func (f *fileSrc) Stop(self *base.GstBaseSrc) bool { + if !f.state.started { + self.Error(gst.DomainResource, gst.ResourceErrorSettings, "FileSrc is not started", "") + return false + } + + if err := f.state.file.Close(); err != nil { + self.Error(gst.DomainResource, gst.ResourceErrorClose, "Failed to close the source file", err.Error()) + return false + } + + f.state.file = nil + f.state.position = 0 + f.state.started = false + + self.Info(gst.DomainResource, "Stopped") + return true +} + +// Fill is called to fill a pre-allocated buffer with the data at offset to the given size. +// Since we declared that we are seekable, we need to support the provided offset not neccesarily matching +// where we currently are in the file. This is why we store the position in the file locally. +func (f *fileSrc) Fill(self *base.GstBaseSrc, offset uint64, size uint, buffer *gst.Buffer) gst.FlowReturn { + if !f.state.started || f.state.file == nil { + self.Error(gst.DomainCore, gst.CoreErrorFailed, "Not started yet", "") + return gst.FlowError + } + + if f.state.position != offset { + if _, err := f.state.file.Seek(int64(offset), 0); err != nil { + self.Error(gst.DomainResource, gst.ResourceErrorSeek, + fmt.Sprintf("Failed to seek to %d in file", offset), err.Error()) + return gst.FlowError + } + } + + out := make([]byte, int(size)) + if _, err := f.state.file.Read(out); err != nil && err != io.EOF { + self.Error(gst.DomainResource, gst.ResourceErrorRead, + fmt.Sprintf("Failed to read %d bytes from file at %d", size, offset), err.Error()) + return gst.FlowError + } + + f.state.position = f.state.position + uint64(size) + + bufmap := buffer.Map(gst.MapWrite) + if bufmap == nil { + self.Error(gst.DomainLibrary, gst.LibraryErrorFailed, "Failed to map buffer", "") + return gst.FlowError + } + defer buffer.Unmap() + + bufmap.WriteData(out) + buffer.SetSize(int64(size)) + + return gst.FlowOK +} + +// URIHandler implementations are the methods required by the GstURIHandler interface. + +// GetURI returns the currently configured URI +func (f *fileSrc) GetURI() string { return fmt.Sprintf("file://%s", f.settings.location) } + +// GetURIType returns the types of URI this element supports. +func (f *fileSrc) GetURIType() gst.URIType { return gst.URISource } + +// GetProtocols returns the protcols this element supports. +func (f *fileSrc) GetProtocols() []string { return []string{"file"} } + +// SetURI should set the URI that this element is working on. +func (f *fileSrc) SetURI(uri string) (bool, error) { + if uri == "file://" { + return true, nil + } + err := f.setLocation(uri) + if err != nil { + return false, err + } + return true, nil +} diff --git a/examples/plugins/filesrc/plugin.go b/examples/plugins/filesrc/plugin.go new file mode 100644 index 0000000..4557e7b --- /dev/null +++ b/examples/plugins/filesrc/plugin.go @@ -0,0 +1,46 @@ +// The contents of this file could be generated from markers placed in filesrc.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-file-plugins", + 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, + "gofilesrc", // The name of the element + gst.RankNone, // The rank of the element + &fileSrc{}, // The GoElement implementation for the element + base.ExtendsBaseSrc, // 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_gofilesrc_get_desc +func gst_plugin_gofilesrc_get_desc() unsafe.Pointer { return pluginMeta.Export() } + +// main is left unimplemented since these files are compiled to c-shared. +func main() {} diff --git a/go.sum b/go.sum index 9e4f9b4..dff2a49 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/gotk3/gotk3 v0.4.0 h1:TIuhyQitGeRTxOQIV3AJlYtEWWJpC74JHwAIsxlH8MU= github.com/gotk3/gotk3 v0.4.0/go.mod h1:Eew3QBwAOBTrfFFDmsDE5wZWbcagBL1NUslj1GhRveo= +github.com/gotk3/gotk3 v0.5.2 h1:jbSFvUNMfo3ImM6BWBAkNUxY5piqP3eTc1YFbYy9ecU= 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= diff --git a/gst/app/gst.go.h b/gst/app/gst.go.h index f175d0a..a542082 100644 --- a/gst/app/gst.go.h +++ b/gst/app/gst.go.h @@ -1,5 +1,10 @@ +#ifndef __GST_APP_GO_H__ +#define __GST_APP_GO_H__ + #include #include inline GstAppSink * toGstAppSink (void *p) { return (GST_APP_SINK(p)); } inline GstAppSrc * toGstAppSrc (void *p) { return (GST_APP_SRC(p)); } + +#endif \ No newline at end of file diff --git a/gst/base/doc.go b/gst/base/doc.go new file mode 100644 index 0000000..1ffb439 --- /dev/null +++ b/gst/base/doc.go @@ -0,0 +1,2 @@ +// Package base contains bindings for extendable GStreamer base objects. +package base diff --git a/gst/base/gst.go.h b/gst/base/gst.go.h new file mode 100644 index 0000000..e1e7c7b --- /dev/null +++ b/gst/base/gst.go.h @@ -0,0 +1,9 @@ +#ifndef __GST_BASE_GO_H__ +#define __GST_BASE_GO_H__ + +#include + +inline GstBaseSrc * toGstBaseSrc (void *p) { return GST_BASE_SRC_CAST(p); }; +inline GstBaseSrcClass * toGstBaseSrcClass (void *p) { return (GstBaseSrcClass *)p; }; + +#endif \ No newline at end of file diff --git a/gst/base/gst_base_src.go b/gst/base/gst_base_src.go new file mode 100644 index 0000000..653721f --- /dev/null +++ b/gst/base/gst_base_src.go @@ -0,0 +1,47 @@ +package base + +/* +#include "gst.go.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/tinyzimmer/go-gst/gst" +) + +// GstBaseSrc represents a GstBaseSrc. +type GstBaseSrc struct{ *gst.Element } + +// ToGstBaseSrc returns a GstBaseSrc object for the given object. +func ToGstBaseSrc(obj *gst.Object) *GstBaseSrc { + return &GstBaseSrc{&gst.Element{Object: obj}} +} + +// wrapGstBaseSrc wraps the given unsafe.Pointer in a GstBaseSrc instance. +func wrapGstBaseSrc(obj *C.GstBaseSrc) *GstBaseSrc { + return &GstBaseSrc{gst.FromGstElementUnsafe(unsafe.Pointer(obj))} +} + +// Instance returns the underlying C GstBaseSrc instance +func (g *GstBaseSrc) Instance() *C.GstBaseSrc { + return C.toGstBaseSrc(g.Unsafe()) +} + +// SetFormat sets the default format of the source. This will be the format used for sending +// SEGMENT events and for performing seeks. +// +// If a format of gst.FormatBytes is set, the element will be able to operate in pull mode if the +// IsSeekable returns TRUE. +// +// This function must only be called in when the element is paused. +func (g *GstBaseSrc) SetFormat(format gst.Format) { + C.gst_base_src_set_format(g.Instance(), C.GstFormat(format)) +} + +// 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)) +} diff --git a/gst/base/gst_base_src_exports.go b/gst/base/gst_base_src_exports.go new file mode 100644 index 0000000..f9a8eee --- /dev/null +++ b/gst/base/gst_base_src_exports.go @@ -0,0 +1,198 @@ +package base + +//#include "gst.go.h" +import "C" +import ( + "time" + "unsafe" + + "github.com/tinyzimmer/go-gst/gst" +) + +//export goGstBaseSrcGetCaps +func goGstBaseSrcGetCaps(src *C.GstBaseSrc, filter *C.GstCaps) *C.GstCaps { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + GetCaps(*GstBaseSrc, *gst.Caps) *gst.Caps + }) + res := caller.GetCaps(wrapGstBaseSrc(src), gst.FromGstCapsUnsafe(unsafe.Pointer(filter))) + return (*C.GstCaps)(unsafe.Pointer(res.Instance())) +} + +//export goGstBaseSrcNegotiate +func goGstBaseSrcNegotiate(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Negotiate(*GstBaseSrc) bool + }) + return gboolean(caller.Negotiate(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcFixate +func goGstBaseSrcFixate(src *C.GstBaseSrc, caps *C.GstCaps) *C.GstCaps { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Fixate(*GstBaseSrc, *gst.Caps) *gst.Caps + }) + res := caller.Fixate(wrapGstBaseSrc(src), gst.FromGstCapsUnsafe(unsafe.Pointer(caps))) + return (*C.GstCaps)(unsafe.Pointer(res.Instance())) +} + +//export goGstBaseSrcSetCaps +func goGstBaseSrcSetCaps(src *C.GstBaseSrc, filter *C.GstCaps) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + SetCaps(*GstBaseSrc, *gst.Caps) bool + }) + return gboolean(caller.SetCaps(wrapGstBaseSrc(src), gst.FromGstCapsUnsafe(unsafe.Pointer(filter)))) +} + +//export goGstBaseSrcDecideAllocation +func goGstBaseSrcDecideAllocation(src *C.GstBaseSrc, query *C.GstQuery) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + DecideAllocation(*GstBaseSrc, *gst.Query) bool + }) + return gboolean(caller.DecideAllocation(wrapGstBaseSrc(src), gst.FromGstQueryUnsafe(unsafe.Pointer(query)))) +} + +//export goGstBaseSrcStart +func goGstBaseSrcStart(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Start(*GstBaseSrc) bool + }) + return gboolean(caller.Start(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcStop +func goGstBaseSrcStop(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Stop(*GstBaseSrc) bool + }) + return gboolean(caller.Stop(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcGetTimes +func goGstBaseSrcGetTimes(src *C.GstBaseSrc, buf *C.GstBuffer, start *C.GstClockTime, end *C.GstClockTime) { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + GetTimes(*GstBaseSrc, *gst.Buffer) (start, end time.Duration) + }) + gostart, goend := caller.GetTimes(wrapGstBaseSrc(src), gst.FromGstBufferUnsafe(unsafe.Pointer(buf))) + *start = C.GstClockTime(gostart.Nanoseconds()) + *end = C.GstClockTime(goend.Nanoseconds()) +} + +//export goGstBaseSrcGetSize +func goGstBaseSrcGetSize(src *C.GstBaseSrc, size *C.guint64) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + GetSize(*GstBaseSrc) (bool, int64) + }) + ok, gosize := caller.GetSize(wrapGstBaseSrc(src)) + if !ok { + return gboolean(ok) + } + *size = C.guint64(gosize) + return gboolean(ok) +} + +//export goGstBaseSrcIsSeekable +func goGstBaseSrcIsSeekable(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + IsSeekable(*GstBaseSrc) bool + }) + return gboolean(caller.IsSeekable(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcPrepareSeekSegment +func goGstBaseSrcPrepareSeekSegment(src *C.GstBaseSrc, seek *C.GstEvent, segment *C.GstSegment) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + PrepareSeekSegment(*GstBaseSrc, *gst.Event, *gst.Segment) bool + }) + return gboolean(caller.PrepareSeekSegment(wrapGstBaseSrc(src), gst.FromGstEventUnsafe(unsafe.Pointer(seek)), gst.FromGstSegmentUnsafe(unsafe.Pointer(segment)))) +} + +//export goGstBaseSrcDoSeek +func goGstBaseSrcDoSeek(src *C.GstBaseSrc, segment *C.GstSegment) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + DoSeek(*GstBaseSrc, *gst.Segment) bool + }) + return gboolean(caller.DoSeek(wrapGstBaseSrc(src), gst.FromGstSegmentUnsafe(unsafe.Pointer(segment)))) +} + +//export goGstBaseSrcUnlock +func goGstBaseSrcUnlock(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Unlock(*GstBaseSrc) bool + }) + return gboolean(caller.Unlock(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcUnlockStop +func goGstBaseSrcUnlockStop(src *C.GstBaseSrc) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + UnlockStop(*GstBaseSrc) bool + }) + return gboolean(caller.UnlockStop(wrapGstBaseSrc(src))) +} + +//export goGstBaseSrcQuery +func goGstBaseSrcQuery(src *C.GstBaseSrc, query *C.GstQuery) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Query(*GstBaseSrc, *gst.Query) bool + }) + return gboolean(caller.Query(wrapGstBaseSrc(src), gst.FromGstQueryUnsafe(unsafe.Pointer(query)))) +} + +//export goGstBaseSrcEvent +func goGstBaseSrcEvent(src *C.GstBaseSrc, event *C.GstEvent) C.gboolean { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Event(*GstBaseSrc, *gst.Event) bool + }) + return gboolean(caller.Event(wrapGstBaseSrc(src), gst.FromGstEventUnsafe(unsafe.Pointer(event)))) +} + +//export goGstBaseSrcCreate +func goGstBaseSrcCreate(src *C.GstBaseSrc, offset C.guint64, size C.guint, buf **C.GstBuffer) C.GstFlowReturn { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Create(*GstBaseSrc, uint64, uint) (gst.FlowReturn, *gst.Buffer) + }) + ret, gobuf := caller.Create(wrapGstBaseSrc(src), uint64(offset), uint(size)) + if ret == gst.FlowOK { + C.memcpy(unsafe.Pointer(*buf), unsafe.Pointer(gobuf.Instance()), C.sizeof_GstBuffer) + } + return C.GstFlowReturn(ret) +} + +//export goGstBaseSrcAlloc +func goGstBaseSrcAlloc(src *C.GstBaseSrc, offset C.guint64, size C.guint, buf **C.GstBuffer) C.GstFlowReturn { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Alloc(*GstBaseSrc, uint64, uint) (gst.FlowReturn, *gst.Buffer) + }) + ret, gobuf := caller.Alloc(wrapGstBaseSrc(src), uint64(offset), uint(size)) + if ret == gst.FlowOK { + C.memcpy(unsafe.Pointer(*buf), unsafe.Pointer(gobuf.Instance()), C.sizeof_GstBuffer) + } + return C.GstFlowReturn(ret) +} + +//export goGstBaseSrcFill +func goGstBaseSrcFill(src *C.GstBaseSrc, offset C.guint64, size C.guint, buf *C.GstBuffer) C.GstFlowReturn { + elem := gst.FromObjectUnsafePrivate(unsafe.Pointer(src)) + caller := elem.(interface { + Fill(*GstBaseSrc, uint64, uint, *gst.Buffer) gst.FlowReturn + }) + return C.GstFlowReturn(caller.Fill(wrapGstBaseSrc(src), uint64(offset), uint(size), gst.FromGstBufferUnsafe(unsafe.Pointer(buf)))) +} diff --git a/gst/base/gst_base_src_impl.go b/gst/base/gst_base_src_impl.go new file mode 100644 index 0000000..c76956b --- /dev/null +++ b/gst/base/gst_base_src_impl.go @@ -0,0 +1,236 @@ +package base + +/* +#include "gst.go.h" + +extern GstCaps * goGstBaseSrcGetCaps (GstBaseSrc * src, GstCaps * caps); +extern gboolean goGstBaseSrcNegotiate (GstBaseSrc * src); +extern GstCaps * goGstBaseSrcFixate (GstBaseSrc * src, GstCaps * caps); +extern gboolean goGstBaseSrcSetCaps (GstBaseSrc * src, GstCaps * filter); +extern gboolean goGstBaseSrcDecideAllocation (GstBaseSrc * src, GstQuery * query); +extern gboolean goGstBaseSrcStart (GstBaseSrc * src); +extern gboolean goGstBaseSrcStop (GstBaseSrc * src); +extern void goGstBaseSrcGetTimes (GstBaseSrc * src, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); +extern gboolean goGstBaseSrcGetSize (GstBaseSrc * src, guint64 * size); +extern gboolean goGstBaseSrcIsSeekable (GstBaseSrc * src); +extern gboolean goGstBaseSrcPrepareSeekSegment (GstBaseSrc * src, GstEvent * seek, GstSegment * segment); +extern gboolean goGstBaseSrcDoSeek (GstBaseSrc * src, GstSegment * segment); +extern gboolean goGstBaseSrcUnlock (GstBaseSrc * src); +extern gboolean goGstBaseSrcUnlockStop (GstBaseSrc * src); +extern gboolean goGstBaseSrcQuery (GstBaseSrc * src, GstQuery * query); +extern gboolean goGstBaseSrcEvent (GstBaseSrc * src, GstEvent * event); +extern GstFlowReturn goGstBaseSrcCreate (GstBaseSrc * src, guint64 offset, guint size, GstBuffer ** buffer); +extern GstFlowReturn goGstBaseSrcAlloc (GstBaseSrc * src, guint64 offset, guint size, GstBuffer ** buffer); +extern GstFlowReturn goGstBaseSrcFill (GstBaseSrc * src, guint64 offset, guint size, GstBuffer * buffer); + + +void setGstBaseSrcGetCaps (GstBaseSrcClass * klass) { klass->get_caps = goGstBaseSrcGetCaps; } +void setGstBaseSrcNegotiate (GstBaseSrcClass * klass) { klass->negotiate = goGstBaseSrcNegotiate; } +void setGstBaseSrcFixate (GstBaseSrcClass * klass) { klass->fixate = goGstBaseSrcFixate; } +void setGstBaseSrcSetCaps (GstBaseSrcClass * klass) { klass->set_caps = goGstBaseSrcSetCaps; } +void setGstBaseSrcDecideAllocation (GstBaseSrcClass * klass) { klass->decide_allocation = goGstBaseSrcDecideAllocation; } +void setGstBaseSrcStart (GstBaseSrcClass * klass) { klass->start = goGstBaseSrcStart; } +void setGstBaseSrcStop (GstBaseSrcClass * klass) { klass->stop = goGstBaseSrcStop; } +void setGstBaseSrcGetTimes (GstBaseSrcClass * klass) { klass->get_times = goGstBaseSrcGetTimes; } +void setGstBaseSrcGetSize (GstBaseSrcClass * klass) { klass->get_size = goGstBaseSrcGetSize; } +void setGstBaseSrcIsSeekable (GstBaseSrcClass * klass) { klass->is_seekable = goGstBaseSrcIsSeekable; } +void setGstBaseSrcPrepareSeekSegment (GstBaseSrcClass * klass) { klass->prepare_seek_segment = goGstBaseSrcPrepareSeekSegment; } +void setGstBaseSrcDoSeek (GstBaseSrcClass * klass) { klass->do_seek = goGstBaseSrcDoSeek; } +void setGstBaseSrcUnlock (GstBaseSrcClass * klass) { klass->unlock = goGstBaseSrcUnlock; } +void setGstBaseSrcUnlockStop (GstBaseSrcClass * klass) { klass->unlock_stop = goGstBaseSrcUnlockStop; } +void setGstBaseSrcQuery (GstBaseSrcClass * klass) { klass->query = goGstBaseSrcQuery; } +void setGstBaseSrcEvent (GstBaseSrcClass * klass) { klass->event = goGstBaseSrcEvent; } +void setGstBaseSrcCreate (GstBaseSrcClass * klass) { klass->create = goGstBaseSrcCreate; } +void setGstBaseSrcAlloc (GstBaseSrcClass * klass) { klass->alloc = goGstBaseSrcAlloc; } +void setGstBaseSrcFill (GstBaseSrcClass * klass) { klass->fill = goGstBaseSrcFill; } + +*/ +import "C" +import ( + "time" + "unsafe" + + "github.com/gotk3/gotk3/glib" + "github.com/tinyzimmer/go-gst/gst" +) + +var ( + // ExtendsBaseSrc is an Extendable for extending a GstBaseSrc + ExtendsBaseSrc gst.Extendable = &extendsBaseSrc{} +) + +// GstBaseSrcImpl is the documented interface for an element extending a GstBaseSrc. 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 GstBaseSrcImpl interface { + // GetCaps retrieves the caps for this class. + GetCaps(*GstBaseSrc, *gst.Caps) *gst.Caps + // Negotiate decides on the caps for this source. + Negotiate(*GstBaseSrc) bool + // Fixate is called if, during negotiation, caps need fixating. + Fixate(*GstBaseSrc, *gst.Caps) *gst.Caps + // SetCaps is used to notify this class of new caps. + SetCaps(*GstBaseSrc, *gst.Caps) bool + // DecideAllocation sets up an allocation query. + DecideAllocation(*GstBaseSrc, *gst.Query) bool + // Start the source, ideal for opening resources. + Start(*GstBaseSrc) bool + // Stop the source, ideal for closing resources. + Stop(*GstBaseSrc) bool + // GetTimes should, given a buffer, return start and stop time when it should be pushed. + // The base class will sync on the clock using these times. + GetTimes(*GstBaseSrc, *gst.Buffer) (start, end time.Duration) + // GetSize should get the total size of the resource in bytes. + GetSize(*GstBaseSrc) (bool, int64) + // IsSeekable should check if the resource is seekable. + IsSeekable(*GstBaseSrc) bool + // PrepareSeekSegment prepares the segment on which to perform DoSeek, converting to the + // current basesrc format. + PrepareSeekSegment(*GstBaseSrc, *gst.Event, *gst.Segment) bool + // DoSeek is used to notify subclasses of a seek. + DoSeek(*GstBaseSrc, *gst.Segment) bool + // Unlock should unlock any pending access to the resource. Subclasses should perform the unlock + // ASAP. + Unlock(*GstBaseSrc) bool + // UnlockStop should clear any pending unlock request, as we succeeded in unlocking. + UnlockStop(*GstBaseSrc) bool + // Query is used to notify subclasses of a query. + Query(*GstBaseSrc, *gst.Query) bool + // Event is used to notify subclasses of an event. + Event(*GstBaseSrc, *gst.Event) bool + // Create asks the subclass to create a buffer with offset and size. The default implementation + // will call alloc and fill. + Create(self *GstBaseSrc, offset uint64, size uint) (gst.FlowReturn, *gst.Buffer) + // Alloc asks the subclass to allocate an output buffer. The default implementation will use the negotiated + // allocator. + Alloc(self *GstBaseSrc, offset uint64, size uint) (gst.FlowReturn, *gst.Buffer) + // Fill asks the subclass to fill the buffer with data from offset and size. + Fill(self *GstBaseSrc, offset uint64, size uint, buffer *gst.Buffer) gst.FlowReturn +} + +type extendsBaseSrc struct{} + +func (e *extendsBaseSrc) Type() glib.Type { return glib.Type(C.gst_base_src_get_type()) } +func (e *extendsBaseSrc) ClassSize() int64 { return int64(C.sizeof_GstBaseSrcClass) } +func (e *extendsBaseSrc) InstanceSize() int64 { return int64(C.sizeof_GstBaseSrc) } + +// InitClass iterates the methods provided by the element and overrides any provided +// in the virtual methods. +func (e *extendsBaseSrc) InitClass(klass unsafe.Pointer, elem gst.GoElement) { + class := C.toGstBaseSrcClass(klass) + + if _, ok := elem.(interface { + GetCaps(*GstBaseSrc, *gst.Caps) *gst.Caps + }); ok { + C.setGstBaseSrcGetCaps(class) + } + + if _, ok := elem.(interface { + Negotiate(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcNegotiate(class) + } + + if _, ok := elem.(interface { + Fixate(*GstBaseSrc, *gst.Caps) *gst.Caps + }); ok { + C.setGstBaseSrcFixate(class) + } + + if _, ok := elem.(interface { + SetCaps(*GstBaseSrc, *gst.Caps) bool + }); ok { + C.setGstBaseSrcSetCaps(class) + } + + if _, ok := elem.(interface { + DecideAllocation(*GstBaseSrc, *gst.Query) bool + }); ok { + C.setGstBaseSrcDecideAllocation(class) + } + + if _, ok := elem.(interface { + Start(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcStart(class) + } + + if _, ok := elem.(interface { + Stop(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcStop(class) + } + + if _, ok := elem.(interface { + GetTimes(*GstBaseSrc, *gst.Buffer) (start, end time.Duration) + }); ok { + C.setGstBaseSrcGetTimes(class) + } + + if _, ok := elem.(interface { + GetSize(*GstBaseSrc) (bool, int64) + }); ok { + C.setGstBaseSrcGetSize(class) + } + + if _, ok := elem.(interface { + IsSeekable(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcIsSeekable(class) + } + + if _, ok := elem.(interface { + PrepareSeekSegment(*GstBaseSrc, *gst.Event, *gst.Segment) bool + }); ok { + C.setGstBaseSrcPrepareSeekSegment(class) + } + + if _, ok := elem.(interface { + DoSeek(*GstBaseSrc, *gst.Segment) bool + }); ok { + C.setGstBaseSrcDoSeek(class) + } + + if _, ok := elem.(interface { + Unlock(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcUnlock(class) + } + + if _, ok := elem.(interface { + UnlockStop(*GstBaseSrc) bool + }); ok { + C.setGstBaseSrcUnlockStop(class) + } + + if _, ok := elem.(interface { + Query(*GstBaseSrc, *gst.Query) bool + }); ok { + C.setGstBaseSrcQuery(class) + } + + if _, ok := elem.(interface { + Event(*GstBaseSrc, *gst.Event) bool + }); ok { + C.setGstBaseSrcEvent(class) + } + + if _, ok := elem.(interface { + Create(self *GstBaseSrc, offset uint64, size uint) (gst.FlowReturn, *gst.Buffer) + }); ok { + C.setGstBaseSrcCreate(class) + } + + if _, ok := elem.(interface { + Alloc(self *GstBaseSrc, offset uint64, size uint) (gst.FlowReturn, *gst.Buffer) + }); ok { + C.setGstBaseSrcAlloc(class) + } + + if _, ok := elem.(interface { + Fill(self *GstBaseSrc, offset uint64, size uint, buffer *gst.Buffer) gst.FlowReturn + }); ok { + C.setGstBaseSrcFill(class) + } +} diff --git a/gst/base/pkg_config.go b/gst/base/pkg_config.go new file mode 100644 index 0000000..a65ba9c --- /dev/null +++ b/gst/base/pkg_config.go @@ -0,0 +1,7 @@ +package base + +/* +#cgo pkg-config: gstreamer-1.0 gstreamer-base-1.0 +#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall +*/ +import "C" diff --git a/gst/base/util.go b/gst/base/util.go new file mode 100644 index 0000000..21edef5 --- /dev/null +++ b/gst/base/util.go @@ -0,0 +1,12 @@ +package base + +//#include "gst.go.h" +import "C" + +// gboolean converts a go bool to a C.gboolean. +func gboolean(b bool) C.gboolean { + if b { + return C.gboolean(1) + } + return C.gboolean(0) +} diff --git a/gst/c_util.go b/gst/c_util.go index 9e1268d..b700c32 100644 --- a/gst/c_util.go +++ b/gst/c_util.go @@ -111,3 +111,13 @@ func glistToStreamSlice(glist *C.GList) []*Stream { }) return out } + +func glistToPadTemplateSlice(glist *C.GList) []*PadTemplate { + l := glib.WrapList(uintptr(unsafe.Pointer(&glist))) + out := make([]*PadTemplate, 0) + l.FreeFull(func(item interface{}) { + tmpl := item.(*C.GstPadTemplate) + out = append(out, wrapPadTemplate(toGObject(unsafe.Pointer(tmpl)))) + }) + return out +} diff --git a/gst/cgo_exports.go b/gst/cgo_exports.go index 53cc764..5b4c30f 100644 --- a/gst/cgo_exports.go +++ b/gst/cgo_exports.go @@ -3,10 +3,13 @@ package gst // CGO exports have to be defined in a separate file from where they are used or else // there will be double linkage issues. -// #include +/* +#include +*/ import "C" import ( + "reflect" "unsafe" "github.com/gotk3/gotk3/glib" @@ -16,6 +19,7 @@ import ( //export goElementCallAsync func goElementCallAsync(element *C.GstElement, userData C.gpointer) { iface := gopointer.Restore(unsafe.Pointer(userData)) + defer gopointer.Unref(unsafe.Pointer(userData)) f := iface.(func()) f() } @@ -221,3 +225,115 @@ func goClockCb(gclock *C.GstClock, clockTime C.GstClockTime, clockID C.GstClockI clock := wrapClock(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(gclock))}) return gboolean(cb(clock, ClockTime(clockTime))) } + +//export goPluginInit +func goPluginInit(plugin *C.GstPlugin, userData C.gpointer) C.gboolean { + ptr := unsafe.Pointer(userData) + defer gopointer.Unref(ptr) + funcIface := gopointer.Restore(ptr) + cb, ok := funcIface.(PluginInitFunc) + if !ok { + return gboolean(false) + } + return gboolean(cb(wrapPlugin(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(plugin))}))) +} + +//export goGlobalPluginInit +func goGlobalPluginInit(plugin *C.GstPlugin) C.gboolean { + return gboolean(globalPluginInit(wrapPlugin(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(plugin))}))) +} + +//export goClassInit +func goClassInit(klass C.gpointer, klassData C.gpointer) { + registerMutex.Lock() + defer registerMutex.Unlock() + + ptr := unsafe.Pointer(klassData) + iface := gopointer.Restore(ptr) + defer gopointer.Unref(ptr) + + data := iface.(*classData) + registeredClasses[klass] = data.elem + + data.ext.InitClass(unsafe.Pointer(klass), data.elem) + + C.g_type_class_add_private(klass, C.gsize(unsafe.Sizeof(uintptr(0)))) + data.elem.ClassInit(wrapElementClass(klass)) +} + +//export goInstanceInit +func goInstanceInit(obj *C.GTypeInstance, klass C.gpointer) { + registerMutex.Lock() + defer registerMutex.Unlock() + + elem := registeredClasses[klass].New() + registeredClasses[klass] = elem + + ptr := gopointer.Save(elem) + private := C.g_type_instance_get_private(obj, registeredTypes[reflect.TypeOf(registeredClasses[klass]).String()]) + C.memcpy(unsafe.Pointer(private), unsafe.Pointer(&ptr), C.gulong(unsafe.Sizeof(uintptr(0)))) +} + +//export goURIHdlrGetURIType +func goURIHdlrGetURIType(gtype C.GType) C.GstURIType { + return C.GstURIType(globalURIHdlr.GetURIType()) +} + +//export goURIHdlrGetProtocols +func goURIHdlrGetProtocols(gtype C.GType) **C.gchar { + protocols := globalURIHdlr.GetProtocols() + size := C.size_t(unsafe.Sizeof((*C.gchar)(nil))) + length := C.size_t(len(protocols)) + arr := (**C.gchar)(C.malloc(length * size)) + view := (*[1 << 30]*C.gchar)(unsafe.Pointer(arr))[0:len(protocols):len(protocols)] + for i, proto := range protocols { + view[i] = (*C.gchar)(C.CString(proto)) + } + return arr +} + +//export goURIHdlrGetURI +func goURIHdlrGetURI(hdlr *C.GstURIHandler) *C.gchar { + iface := FromObjectUnsafePrivate(unsafe.Pointer(hdlr)) + return (*C.gchar)(unsafe.Pointer(C.CString(iface.(URIHandler).GetURI()))) +} + +//export goURIHdlrSetURI +func goURIHdlrSetURI(hdlr *C.GstURIHandler, uri *C.gchar, gerr **C.GError) C.gboolean { + iface := FromObjectUnsafePrivate(unsafe.Pointer(hdlr)) + ok, err := iface.(URIHandler).SetURI(C.GoString(uri)) + if err != nil { + C.g_set_error_literal(gerr, newQuarkFromString(string(DomainLibrary)), C.gint(LibraryErrorSettings), C.CString(err.Error())) + } + return gboolean(ok) +} + +//export goObjectSetProperty +func goObjectSetProperty(obj *C.GObject, propID C.guint, val *C.GValue, param *C.GParamSpec) { + iface := FromObjectUnsafePrivate(unsafe.Pointer(obj)) + iface.SetProperty(wrapObject(toGObject(unsafe.Pointer(obj))), uint(propID-1), glib.ValueFromNative(unsafe.Pointer(val))) +} + +//export goObjectGetProperty +func goObjectGetProperty(obj *C.GObject, propID C.guint, value *C.GValue, param *C.GParamSpec) { + iface := FromObjectUnsafePrivate(unsafe.Pointer(obj)) + val := iface.GetProperty(wrapObject(toGObject(unsafe.Pointer(obj))), uint(propID-1)) + if val == nil { + return + } + C.g_value_copy((*C.GValue)(unsafe.Pointer(val.GValue)), value) +} + +//export goObjectConstructed +func goObjectConstructed(obj *C.GObject) { + iface := FromObjectUnsafePrivate(unsafe.Pointer(obj)) + iface.Constructed(wrapObject(toGObject(unsafe.Pointer(obj)))) +} + +//export goObjectFinalize +func goObjectFinalize(obj *C.GObject, klass C.gpointer) { + registerMutex.Lock() + defer registerMutex.Unlock() + delete(registeredClasses, klass) + gopointer.Unref(privateFromObj(unsafe.Pointer(obj))) +} diff --git a/gst/constants.go b/gst/constants.go index 70df1db..cc25b59 100644 --- a/gst/constants.go +++ b/gst/constants.go @@ -5,6 +5,32 @@ import "C" import "unsafe" +// Version represents information about the current GST version. +type Version int + +const ( + // VersionMajor is the major version number of the GStreamer core. + VersionMajor Version = C.GST_VERSION_MAJOR + // VersionMinor is the minor version number of the GStreamer core. + VersionMinor Version = C.GST_VERSION_MINOR +) + +// License represents a type of license used on a plugin. +type License string + +// Types of licenses +const ( + LicenseLGPL License = "LGPL" + LicenseGPL License = "GPL" + LicenseQPL License = "QPL" + LicenseGPLQPL License = "GPL/QPL" + LicenseMPL License = "MPL" + LicenseBSD License = "BSD" + LicenseMIT License = "MIT/X11" + LicenseProprietary License = "Proprietary" + LicenseUnknown License = "unknown" +) + // GFraction is a helper structure for building fractions for functions that require them. type GFraction struct { num, denom int @@ -382,20 +408,20 @@ type PadDirection int // Type casting of pad directions const ( - PadUnknown PadDirection = C.GST_PAD_UNKNOWN // (0) - the direction is unknown - PadSource PadDirection = C.GST_PAD_SRC // (1) - the pad is a source pad - PadSink PadDirection = C.GST_PAD_SINK // (2) - the pad is a sink pad + PadDirectionUnknown PadDirection = C.GST_PAD_UNKNOWN // (0) - the direction is unknown + PadDirectionSource PadDirection = C.GST_PAD_SRC // (1) - the pad is a source pad + PadDirectionSink PadDirection = C.GST_PAD_SINK // (2) - the pad is a sink pad ) // String implements a Stringer on PadDirection. func (p PadDirection) String() string { switch p { - case PadUnknown: - return "Unknown" - case PadSource: - return "Src" - case PadSink: - return "Sink" + case PadDirectionUnknown: + return "unknown" + case PadDirectionSource: + return "src" + case PadDirectionSink: + return "sink" } return "" } @@ -424,20 +450,20 @@ type PadPresence int // Type casting of pad presences const ( - PadAlways PadPresence = C.GST_PAD_ALWAYS // (0) - the pad is always available - PadSometimes PadPresence = C.GST_PAD_SOMETIMES // (1) - the pad will become available depending on the media stream - PadRequest PadPresence = C.GST_PAD_REQUEST // (2) - the pad is only available on request with gst_element_request_pad. + PadPresenceAlways PadPresence = C.GST_PAD_ALWAYS // (0) - the pad is always available + PadPresenceSometimes PadPresence = C.GST_PAD_SOMETIMES // (1) - the pad will become available depending on the media stream + PadPresenceRequest PadPresence = C.GST_PAD_REQUEST // (2) - the pad is only available on request with gst_element_request_pad. ) // String implements a stringer on PadPresence. func (p PadPresence) String() string { switch p { - case PadAlways: - return "Always" - case PadSometimes: - return "Sometimes" - case PadRequest: - return "Request" + case PadPresenceAlways: + return "always" + case PadPresenceSometimes: + return "sometimes" + case PadPresenceRequest: + return "request" } return "" } diff --git a/gst/g_error.go b/gst/g_error.go index 441b689..553b712 100644 --- a/gst/g_error.go +++ b/gst/g_error.go @@ -7,7 +7,7 @@ type GError struct { structure *Structure // used for message constructors - code int + code ErrorCode } // Message is an alias to `Error()`. It's for clarity when this object @@ -23,9 +23,11 @@ func (e *GError) DebugString() string { return e.debugStr } // Structure returns the structure of the error message which may contain additional metadata. func (e *GError) Structure() *Structure { return e.structure } -// NewGError wraps the given error inside a GError (to be used with message constructors). The code -// is optional and allows for adding additional "types" to the error. -func NewGError(code int, err error) *GError { +// Code returns the error code of the error message. +func (e *GError) Code() ErrorCode { return e.code } + +// NewGError wraps the given error inside a GError (to be used with message constructors). +func NewGError(code ErrorCode, err error) *GError { return &GError{ errMsg: err.Error(), code: code, diff --git a/gst/g_object_class.go b/gst/g_object_class.go new file mode 100644 index 0000000..cbe261c --- /dev/null +++ b/gst/g_object_class.go @@ -0,0 +1,75 @@ +package gst + +/* +#include "gst.go.h" + +extern GstURIType goURIHdlrGetURIType (GType type); +extern const gchar * const * goURIHdlrGetProtocols (GType type); +extern gchar * goURIHdlrGetURI (GstURIHandler * handler); +extern gboolean goURIHdlrSetURI (GstURIHandler * handler, + const gchar * uri, + GError ** error); + +void uriHandlerInit (gpointer iface, gpointer iface_data) +{ + ((GstURIHandlerInterface*)iface)->get_type = goURIHdlrGetURIType; + ((GstURIHandlerInterface*)iface)->get_protocols = goURIHdlrGetProtocols; + ((GstURIHandlerInterface*)iface)->get_uri = goURIHdlrGetURI; + ((GstURIHandlerInterface*)iface)->set_uri = goURIHdlrSetURI; +} +*/ +import "C" +import ( + "unsafe" + + "github.com/gotk3/gotk3/glib" +) + +// ObjectClass is a loose binding around the glib GObjectClass. +// It forms the base of a GstElementClass. +type ObjectClass struct { + ptr *C.GObjectClass +} + +// Unsafe is a convenience wrapper to return the unsafe.Pointer of the underlying C instance. +func (o *ObjectClass) Unsafe() unsafe.Pointer { return unsafe.Pointer(o.ptr) } + +// Instance returns the underlying C GObjectClass pointer +func (o *ObjectClass) Instance() *C.GObjectClass { return o.ptr } + +// InstallProperties will install the given ParameterSpecs to the object class. +// They will be IDed in the order they are provided. +func (o *ObjectClass) InstallProperties(params []*ParameterSpec) { + for idx, prop := range params { + C.g_object_class_install_property( + o.Instance(), + C.guint(idx+1), + prop.paramSpec, + ) + } +} + +// TypeInstance is a loose binding around the glib GTypeInstance. It exposes methods required +// to register the various capabilities of an element. +type TypeInstance struct { + gtype C.GType + gotype GoElement +} + +// AddInterface will add an interface implementation for the type referenced by this object. +func (t *TypeInstance) AddInterface(iface glib.Type) { + ifaceInfo := C.GInterfaceInfo{ + interface_data: nil, + interface_finalize: nil, + } + switch iface { + case InterfaceURIHandler: + globalURIHdlr = t.gotype.(URIHandler) + ifaceInfo.interface_init = C.GInterfaceInitFunc(C.uriHandlerInit) + } + C.g_type_add_interface_static( + (C.GType)(t.gtype), + (C.GType)(iface), + &ifaceInfo, + ) +} diff --git a/gst/gst_parameter_spec.go b/gst/g_parameter_spec.go similarity index 79% rename from gst/gst_parameter_spec.go rename to gst/g_parameter_spec.go index 58f8caf..6701ac2 100644 --- a/gst/gst_parameter_spec.go +++ b/gst/g_parameter_spec.go @@ -13,12 +13,61 @@ import ( // ParameterSpec is a go representation of a C GParamSpec type ParameterSpec struct { paramSpec *C.GParamSpec - Name string - Blurb string - Flags ParameterFlags - ValueType glib.Type - OwnerType glib.Type - DefaultValue *glib.Value + defaultValue *glib.Value +} + +// NewStringParameter returns a new ParameterSpec that will hold a string value. +func NewStringParameter(name, nick, blurb string, defaultValue *string, flags ParameterFlags) *ParameterSpec { + var cdefault *C.gchar + var paramDefault *glib.Value + if defaultValue != nil { + cdefault = C.CString(*defaultValue) + var err error + paramDefault, err = glib.ValueInit(glib.TYPE_STRING) + if err != nil { + return nil + } + paramDefault.SetString(*defaultValue) + } + paramSpec := C.g_param_spec_string( + (*C.gchar)(C.CString(name)), + (*C.gchar)(C.CString(nick)), + (*C.gchar)(C.CString(blurb)), + (*C.gchar)(cdefault), + C.GParamFlags(flags), + ) + return &ParameterSpec{paramSpec: paramSpec, defaultValue: paramDefault} +} + +// Name returns the name of this parameter. +func (p *ParameterSpec) Name() string { + return C.GoString(C.g_param_spec_get_name(p.paramSpec)) +} + +// Blurb returns the blurb for this parameter. +func (p *ParameterSpec) Blurb() string { + return C.GoString(C.g_param_spec_get_blurb(p.paramSpec)) +} + +// Flags returns the flags for this parameter. +func (p *ParameterSpec) Flags() ParameterFlags { + return ParameterFlags(p.paramSpec.flags) +} + +// ValueType returns the GType for the value inside this parameter. +func (p *ParameterSpec) ValueType() glib.Type { + return glib.Type(p.paramSpec.value_type) +} + +// OwnerType returns the Gtype for the owner of this parameter. +func (p *ParameterSpec) OwnerType() glib.Type { + return glib.Type(p.paramSpec.owner_type) +} + +// DefaultValue returns the default value for the parameter if it was included when the object +// was instantiated. Otherwise it returns nil. +func (p *ParameterSpec) DefaultValue() *glib.Value { + return p.defaultValue } // Unref the underlying paramater spec. @@ -114,7 +163,10 @@ type FlagsValue struct { // GetDefaultFlags returns the default flags for this parameter spec. func (p *ParameterSpec) GetDefaultFlags() int { - return int(C.g_value_get_flags((*C.GValue)(p.DefaultValue.Native()))) + if p.DefaultValue() == nil { + return 0 + } + return int(C.g_value_get_flags((*C.GValue)(p.DefaultValue().Native()))) } // GetFlagValues returns the possible flags for this parameter. @@ -135,7 +187,10 @@ func (p *ParameterSpec) GetFlagValues() []*FlagsValue { // GetCaps returns the caps in this parameter if it is of type GST_TYPE_CAPS. func (p *ParameterSpec) GetCaps() *Caps { - caps := C.gst_value_get_caps((*C.GValue)(unsafe.Pointer(p.DefaultValue.Native()))) + if p.DefaultValue() == nil { + return nil + } + caps := C.gst_value_get_caps((*C.GValue)(unsafe.Pointer(p.DefaultValue().Native()))) if caps == nil { return nil } @@ -150,8 +205,9 @@ func (p ParameterFlags) Has(b ParameterFlags) bool { return p&b != 0 } // Type casting of GParamFlags const ( - ParameterReadable ParameterFlags = C.G_PARAM_READABLE // the parameter is readable - ParameterWritable = C.G_PARAM_WRITABLE // the parameter is writable + ParameterReadable ParameterFlags = C.G_PARAM_READABLE // the parameter is readable + ParameterWritable = C.G_PARAM_WRITABLE // the parameter is writable + ParameterReadWrite = ParameterReadable | ParameterWritable ParameterConstruct = C.G_PARAM_CONSTRUCT // the parameter will be set upon object construction ParameterConstructOnly = C.G_PARAM_CONSTRUCT_ONLY // the parameter can only be set upon object construction ParameterLaxValidation = C.G_PARAM_LAX_VALIDATION // upon parameter conversion (see g_param_value_convert()) strict validation is not required diff --git a/gst/gst.go.h b/gst/gst.go.h index a23e314..d2af847 100644 --- a/gst/gst.go.h +++ b/gst/gst.go.h @@ -3,11 +3,15 @@ #include #include +#include /* Type Castings */ +inline GType objectGType (GObject *obj) { return G_OBJECT_TYPE(obj); }; +inline GObjectClass * toGObjectClass (void *p) { return (G_OBJECT_CLASS(p)); } + inline GstAllocator * toGstAllocator (void *p) { return (GST_ALLOCATOR_CAST(p)); } inline GstBin * toGstBin (void *p) { return (GST_BIN(p)); } inline GstBufferList * toGstBufferList (void *p) { return (GST_BUFFER_LIST(p)); } @@ -20,6 +24,7 @@ inline GstClock * toGstClock (void *p) { return (GST_CLO inline GstContext * toGstContext (void *p) { return (GST_CONTEXT_CAST(p)); } inline GstDevice * toGstDevice (void *p) { return (GST_DEVICE_CAST(p)); } inline GstElementFactory * toGstElementFactory (void *p) { return (GST_ELEMENT_FACTORY(p)); } +inline GstElementClass * toGstElementClass (void *p) { return (GST_ELEMENT_CLASS(p)); } inline GstElement * toGstElement (void *p) { return (GST_ELEMENT(p)); } inline GstEvent * toGstEvent (void *p) { return (GST_EVENT(p)); } inline GstGhostPad * toGstGhostPad (void *p) { return (GST_GHOST_PAD(p)); } diff --git a/gst/gst_buffer.go b/gst/gst_buffer.go index 153cdd7..94874f4 100644 --- a/gst/gst_buffer.go +++ b/gst/gst_buffer.go @@ -39,6 +39,12 @@ type Buffer struct { mapInfo *MapInfo } +// FromGstBufferUnsafe wraps the given C GstBuffer in the go type. It is meant for internal usage +// and exported for visibility to other packages. +func FromGstBufferUnsafe(buf unsafe.Pointer) *Buffer { + return wrapBuffer((*C.GstBuffer)(buf)) +} + // NewEmptyBuffer returns a new empty buffer. func NewEmptyBuffer() *Buffer { return wrapBuffer(C.gst_buffer_new()) diff --git a/gst/gst_element.go b/gst/gst_element.go index 425390f..e320d92 100644 --- a/gst/gst_element.go +++ b/gst/gst_element.go @@ -21,6 +21,8 @@ import "C" import ( "fmt" + "path" + "runtime" "unsafe" "github.com/gotk3/gotk3/glib" @@ -30,6 +32,9 @@ import ( // Element is a Go wrapper around a GstElement. type Element struct{ *Object } +// ToElement returns an Element object for the given Object. +func ToElement(obj *Object) *Element { return &Element{Object: obj} } + // ElementLinkMany is a go implementation of `gst_element_link_many` to compensate for // no variadic functions in cgo. func ElementLinkMany(elems ...*Element) error { @@ -45,24 +50,145 @@ func ElementLinkMany(elems ...*Element) error { return nil } +// Rank represents a level of importance when autoplugging elements. +type Rank uint + +// For now just a single RankNone is provided +const ( + RankNone Rank = 0 +) + +// RegisterElement creates a new elementfactory capable of instantiating objects of the given GoElement +// and adds the factory to the plugin. A higher rank means more importance when autoplugging. +func RegisterElement(plugin *Plugin, name string, rank Rank, elem GoElement, extends Extendable) bool { + return gobool(C.gst_element_register( + plugin.Instance(), + C.CString(name), + C.guint(rank), + gtypeForGoElement(name, elem, extends), + )) +} + // Instance returns the underlying GstElement instance. func (e *Element) Instance() *C.GstElement { return C.toGstElement(e.Unsafe()) } -// Link wraps gst_element_link and links this element to the given one. -func (e *Element) Link(elem *Element) error { - if ok := C.gst_element_link((*C.GstElement)(e.Instance()), (*C.GstElement)(elem.Instance())); !gobool(ok) { - return fmt.Errorf("Failed to link %s to %s", e.Name(), elem.Name()) +// AbortState aborts the state change of the element. This function is used by elements that do asynchronous state changes +// and find out something is wrong. +func (e *Element) AbortState() { C.gst_element_abort_state(e.Instance()) } + +// AddPad adds a pad (link point) to element. pad's parent will be set to element +// +// Pads are automatically activated when added in the PAUSED or PLAYING state. +// +// The pad and the element should be unlocked when calling this function. +// +// This function will emit the pad-added signal on the element. +func (e *Element) AddPad(pad *Pad) bool { + return gobool(C.gst_element_add_pad(e.Instance(), pad.Instance())) +} + +// BlockSetState is like SetState except it will block until the transition +// is complete. +func (e *Element) BlockSetState(state State) error { + if err := e.SetState(state); err != nil { + return err } + cState := C.GstState(state) + var curState C.GstState + C.gst_element_get_state( + (*C.GstElement)(e.Instance()), + (*C.GstState)(unsafe.Pointer(&curState)), + (*C.GstState)(unsafe.Pointer(&cState)), + C.GstClockTime(ClockTimeNone), + ) return nil } -// LinkFiltered wraps gst_element_link_filtered and link this element to the given one -// using the provided sink caps. -func (e *Element) LinkFiltered(elem *Element, caps *Caps) error { - if ok := C.gst_element_link_filtered((*C.GstElement)(e.Instance()), (*C.GstElement)(elem.Instance()), (*C.GstCaps)(caps.Instance())); !gobool(ok) { - return fmt.Errorf("Failed to link %s to %s with provider caps", e.Name(), elem.Name()) +// CallAsync calls f from another thread. This is to be used for cases when a state change has to be performed from a streaming +// thread, directly via SetState or indirectly e.g. via SEEK events. +// +// Calling those functions directly from the streaming thread will cause deadlocks in many situations, as they might involve waiting +// for the streaming thread to shut down from this very streaming thread. +func (e *Element) CallAsync(f func()) { + ptr := gopointer.Save(f) + C.gst_element_call_async( + e.Instance(), + C.GstElementCallAsyncFunc(C.cgoElementCallAsync), + (C.gpointer)(unsafe.Pointer(ptr)), + C.GDestroyNotify(C.cgoElementAsyncDestroyNotify), + ) +} + +// Connect connects to the given signal on this element, and applies f as the callback. The callback must +// match the signature of the expected callback from the documentation. However, instead of specifying C types +// for arguments specify the go-gst equivalent (e.g. *gst.Element for almost all GstElement derivitives). +// +// This and the Emit() method may get moved down the hierarchy to the Object level at some point, since +func (e *Element) Connect(signal string, f interface{}) (glib.SignalHandle, error) { + // Elements are sometimes their own type unique from TYPE_ELEMENT. So make sure a type marshaler + // is registered for whatever this type is. Use the built-in element marshaler. + if e.TypeFromInstance() != glib.Type(C.GST_TYPE_ELEMENT) { + glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) } - return nil + return e.Object.Connect(signal, f, nil) +} + +// Emit is a wrapper around g_signal_emitv() and emits the signal specified by the string s to an Object. Arguments to +// callback functions connected to this signal must be specified in args. Emit() returns an interface{} which must be +// type asserted as the Go equivalent type to the return value for native C callback. +// +// Note that this code is unsafe in that the types of values in args are not checked against whether they are suitable +// for the callback. +func (e *Element) Emit(signal string, args ...interface{}) (interface{}, error) { + // We are wrapping this for the same reason as Connect. + if e.TypeFromInstance() != glib.Type(C.GST_TYPE_ELEMENT) { + glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) + } + return e.Object.Emit(signal, args...) +} + +// Info is a convenience wrapper for posting an info message from inside an element. Only to be used from +// plugins. +func (e *Element) Info(domain Domain, text string) { + function, file, line, _ := runtime.Caller(1) + e.MessageFull(MessageInfo, domain, ErrorCode(0), "", text, path.Base(file), runtime.FuncForPC(function).Name(), line) +} + +// Warning is a convenience wrapper for posting a warning message from inside an element. Only to be used from +// plugins. +func (e *Element) Warning(domain Domain, text string) { + function, file, line, _ := runtime.Caller(1) + e.MessageFull(MessageWarning, domain, ErrorCode(0), "", text, path.Base(file), runtime.FuncForPC(function).Name(), line) +} + +// Error is a convenience wrapper for posting an error message from inside an element. Only to be used from +// plugins. +func (e *Element) Error(domain Domain, code ErrorCode, text, debug string) { + function, file, line, _ := runtime.Caller(1) + e.MessageFull(MessageError, domain, code, text, debug, path.Base(file), runtime.FuncForPC(function).Name(), line) +} + +// MessageFull will post an error, warning, or info message on the bus from inside an element. Only to be used +// from plugins. +func (e *Element) MessageFull(msgType MessageType, domain Domain, code ErrorCode, text, debug, file, function string, line int) { + var cTxt, cDbg unsafe.Pointer + if text != "" { + cTxt = unsafe.Pointer(C.CString(text)) + } + if debug != "" { + cDbg = unsafe.Pointer(C.CString(debug)) + } + C.gst_element_message_full( + e.Instance(), + C.GstMessageType(msgType), + newQuarkFromString(string(domain)), + C.gint(code), + (*C.gchar)(cTxt), + (*C.gchar)(cDbg), + C.CString(file), + C.CString(function), + C.gint(line), + ) } // GetBus returns the GstBus for retrieving messages from this element. This function returns @@ -85,37 +211,6 @@ func (e *Element) GetClock() *Clock { return wrapClock(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(cClock))}) } -// GetState returns the current state of this element. -func (e *Element) GetState() State { - return State(e.Instance().current_state) -} - -// SetState sets the target state for this element. -func (e *Element) SetState(state State) error { - stateRet := C.gst_element_set_state((*C.GstElement)(e.Instance()), C.GstState(state)) - if stateRet == C.GST_STATE_CHANGE_FAILURE { - return fmt.Errorf("Failed to change state to %s", state.String()) - } - return nil -} - -// BlockSetState is like SetState except it will block until the transition -// is complete. -func (e *Element) BlockSetState(state State) error { - if err := e.SetState(state); err != nil { - return err - } - cState := C.GstState(state) - var curState C.GstState - C.gst_element_get_state( - (*C.GstElement)(e.Instance()), - (*C.GstState)(unsafe.Pointer(&curState)), - (*C.GstState)(unsafe.Pointer(&cState)), - C.GstClockTime(ClockTimeNone), - ) - return nil -} - // GetFactory returns the factory that created this element. No refcounting is needed. func (e *Element) GetFactory() *ElementFactory { factory := C.gst_element_get_factory((*C.GstElement)(e.Instance())) @@ -136,18 +231,6 @@ func (e *Element) GetPads() []*Pad { return out } -// GetStaticPad retrieves a pad from element by name. This version only retrieves -// already-existing (i.e. 'static') pads. -func (e *Element) GetStaticPad(name string) *Pad { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - pad := C.gst_element_get_static_pad(e.Instance(), (*C.gchar)(unsafe.Pointer(cname))) - if pad == nil { - return nil - } - return wrapPad(toGObject(unsafe.Pointer(pad))) -} - // GetPadTemplates retrieves a list of the pad templates associated with this element. // The list must not be modified by the calling code. func (e *Element) GetPadTemplates() []*PadTemplate { @@ -164,6 +247,23 @@ func (e *Element) GetPadTemplates() []*PadTemplate { return out } +// GetState returns the current state of this element. +func (e *Element) GetState() State { + return State(e.Instance().current_state) +} + +// GetStaticPad retrieves a pad from element by name. This version only retrieves +// already-existing (i.e. 'static') pads. +func (e *Element) GetStaticPad(name string) *Pad { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + pad := C.gst_element_get_static_pad(e.Instance(), (*C.gchar)(unsafe.Pointer(cname))) + if pad == nil { + return nil + } + return wrapPad(toGObject(unsafe.Pointer(pad))) +} + // Has returns true if this element has the given flags. func (e *Element) Has(flags ElementFlags) bool { return gobool(C.gstObjectFlagIsSet(C.toGstObject(e.Unsafe()), C.GstElementFlags(flags))) @@ -174,34 +274,21 @@ func (e *Element) IsURIHandler() bool { return gobool(C.gstElementIsURIHandler(e.Instance())) } -// URIHandler returns a URIHandler interface if implemented by this element. Otherwise it -// returns nil. Currently this only supports elements built through this package, however, -// inner application elements could still use the interface as a reference implementation. -func (e *Element) URIHandler() URIHandler { - if C.toGstURIHandler(e.Unsafe()) == nil { - return nil +// Link wraps gst_element_link and links this element to the given one. +func (e *Element) Link(elem *Element) error { + if ok := C.gst_element_link((*C.GstElement)(e.Instance()), (*C.GstElement)(elem.Instance())); !gobool(ok) { + return fmt.Errorf("Failed to link %s to %s", e.Name(), elem.Name()) } - return &gstURIHandler{ptr: e.Instance()} + return nil } -// TOCSetter returns a TOCSetter interface if implemented by this element. Otherwise it -// returns nil. Currently this only supports elements built through this package, however, -// inner application elements could still use the interface as a reference implementation. -func (e *Element) TOCSetter() TOCSetter { - if C.toTocSetter(e.Instance()) == nil { - return nil +// LinkFiltered wraps gst_element_link_filtered and link this element to the given one +// using the provided sink caps. +func (e *Element) LinkFiltered(elem *Element, caps *Caps) error { + if ok := C.gst_element_link_filtered((*C.GstElement)(e.Instance()), (*C.GstElement)(elem.Instance()), (*C.GstCaps)(caps.Instance())); !gobool(ok) { + return fmt.Errorf("Failed to link %s to %s with provider caps", e.Name(), elem.Name()) } - return &gstTOCSetter{ptr: e.Instance()} -} - -// TagSetter returns a TagSetter interface if implemented by this element. Otherwise it returns nil. -// This currently only supports elements built through this package's bindings, however, inner application -// elements can still implement the interface themselves if they want. -func (e *Element) TagSetter() TagSetter { - if C.toTagSetter(e.Instance()) == nil { - return nil - } - return &gstTagSetter{ptr: e.Instance()} + return nil } // Query performs a query on the given element. @@ -253,32 +340,13 @@ func (e *Element) SendEvent(ev *Event) bool { return gobool(C.gst_element_send_event(e.Instance(), ev.Instance())) } -// Connect connects to the given signal on this element, and applies f as the callback. The callback must -// match the signature of the expected callback from the documentation. However, instead of specifying C types -// for arguments specify the go-gst equivalent (e.g. *gst.Element for almost all GstElement derivitives). -// -// This and the Emit() method may get moved down the hierarchy to the Object level at some point, since -func (e *Element) Connect(signal string, f interface{}) (glib.SignalHandle, error) { - // Elements are sometimes their own type unique from TYPE_ELEMENT. So make sure a type marshaler - // is registered for whatever this type is. Use the built-in element marshaler. - if e.TypeFromInstance() != glib.Type(C.GST_TYPE_ELEMENT) { - glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) +// SetState sets the target state for this element. +func (e *Element) SetState(state State) error { + stateRet := C.gst_element_set_state((*C.GstElement)(e.Instance()), C.GstState(state)) + if stateRet == C.GST_STATE_CHANGE_FAILURE { + return fmt.Errorf("Failed to change state to %s", state.String()) } - return e.Object.Connect(signal, f, nil) -} - -// Emit is a wrapper around g_signal_emitv() and emits the signal specified by the string s to an Object. Arguments to -// callback functions connected to this signal must be specified in args. Emit() returns an interface{} which must be -// type asserted as the Go equivalent type to the return value for native C callback. -// -// Note that this code is unsafe in that the types of values in args are not checked against whether they are suitable -// for the callback. -func (e *Element) Emit(signal string, args ...interface{}) (interface{}, error) { - // We are wrapping this for the same reason as Connect. - if e.TypeFromInstance() != glib.Type(C.GST_TYPE_ELEMENT) { - glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) - } - return e.Object.Emit(signal, args...) + return nil } // SyncStateWithParent tries to change the state of the element to the same as its parent. If this function returns @@ -287,32 +355,32 @@ func (e *Element) SyncStateWithParent() bool { return gobool(C.gst_element_sync_state_with_parent(e.Instance())) } -// AbortState aborts the state change of the element. This function is used by elements that do asynchronous state changes -// and find out something is wrong. -func (e *Element) AbortState() { C.gst_element_abort_state(e.Instance()) } - -// AddPad adds a pad (link point) to element. pad's parent will be set to element -// -// Pads are automatically activated when added in the PAUSED or PLAYING state. -// -// The pad and the element should be unlocked when calling this function. -// -// This function will emit the pad-added signal on the element. -func (e *Element) AddPad(pad *Pad) bool { - return gobool(C.gst_element_add_pad(e.Instance(), pad.Instance())) +// TOCSetter returns a TOCSetter interface if implemented by this element. Otherwise it +// returns nil. Currently this only supports elements built through this package, however, +// inner application elements could still use the interface as a reference implementation. +func (e *Element) TOCSetter() TOCSetter { + if C.toTocSetter(e.Instance()) == nil { + return nil + } + return &gstTOCSetter{ptr: e.Instance()} } -// CallAsync calls f from another thread. This is to be used for cases when a state change has to be performed from a streaming -// thread, directly via SetState or indirectly e.g. via SEEK events. -// -// Calling those functions directly from the streaming thread will cause deadlocks in many situations, as they might involve waiting -// for the streaming thread to shut down from this very streaming thread. -func (e *Element) CallAsync(f func()) { - ptr := gopointer.Save(f) - C.gst_element_call_async( - e.Instance(), - C.GstElementCallAsyncFunc(C.cgoElementCallAsync), - (C.gpointer)(unsafe.Pointer(ptr)), - C.GDestroyNotify(C.cgoElementAsyncDestroyNotify), - ) +// TagSetter returns a TagSetter interface if implemented by this element. Otherwise it returns nil. +// This currently only supports elements built through this package's bindings, however, inner application +// elements can still implement the interface themselves if they want. +func (e *Element) TagSetter() TagSetter { + if C.toTagSetter(e.Instance()) == nil { + return nil + } + return &gstTagSetter{ptr: e.Instance()} +} + +// URIHandler returns a URIHandler interface if implemented by this element. Otherwise it +// returns nil. Currently this only supports elements built through this package, however, +// inner application elements could still use the interface as a reference implementation. +func (e *Element) URIHandler() URIHandler { + if C.toGstURIHandler(e.Unsafe()) == nil { + return nil + } + return &gstURIHandler{ptr: e.Instance()} } diff --git a/gst/gst_element_class.go b/gst/gst_element_class.go new file mode 100644 index 0000000..54e8af7 --- /dev/null +++ b/gst/gst_element_class.go @@ -0,0 +1,102 @@ +package gst + +/* +#include "gst.go.h" +*/ +import "C" +import "unsafe" + +// ElementClass represents the subclass of an element provided by a plugin. +type ElementClass struct{ *ObjectClass } + +// Instance returns the underlying GstElementClass instance. +func (e *ElementClass) Instance() *C.GstElementClass { + return C.toGstElementClass(e.Unsafe()) +} + +// AddMetadata sets key with the given value in the metadata of the class. +func (e *ElementClass) AddMetadata(key, value string) { + C.gst_element_class_add_static_metadata( + e.Instance(), + (*C.gchar)(C.CString(key)), + (*C.gchar)(C.CString(value)), + ) +} + +// AddPadTemplate adds a padtemplate to an element class. This is mainly used in the +// ClassInit functions of ObjectSubclasses. If a pad template with the same name as an +// already existing one is added the old one is replaced by the new one. +// +// templ's reference count will be incremented, and any floating reference will be removed +func (e *ElementClass) AddPadTemplate(templ *PadTemplate) { + C.gst_element_class_add_pad_template( + e.Instance(), + templ.Instance(), + ) +} + +// AddStaticPadTemplate adds a pad template to an element class based on the pad template templ. The template +// is first converted to a static pad template. +// +// This is mainly used in the ClassInit functions of element implementations. If a pad template with the +// same name already exists, the old one is replaced by the new one. +func (e *ElementClass) AddStaticPadTemplate(templ *PadTemplate) { + staticTmpl := C.GstStaticPadTemplate{ + name_template: templ.Instance().name_template, + direction: templ.Instance().direction, + presence: templ.Instance().presence, + static_caps: C.GstStaticCaps{ + caps: templ.Caps().Instance(), + string: C.CString(templ.Name()), + }, + } + C.gst_element_class_add_static_pad_template( + e.Instance(), + &staticTmpl, + ) +} + +// GetMetadata retrieves the metadata associated with key in the class. +func (e *ElementClass) GetMetadata(key string) string { + ckey := C.CString(key) + defer C.free(unsafe.Pointer(ckey)) + return C.GoString(C.gst_element_class_get_metadata(e.Instance(), (*C.gchar)(ckey))) +} + +// GetPadTemplate retrieves the padtemplate with the given name. No unrefing is necessary. +// If no pad template exists with the given name, nil is returned. +func (e *ElementClass) GetPadTemplate(name string) *PadTemplate { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + tmpl := C.gst_element_class_get_pad_template(e.Instance(), (*C.gchar)(cname)) + if tmpl == nil { + return nil + } + return wrapPadTemplate(toGObject(unsafe.Pointer(tmpl))) +} + +// GetAllPadTemplates retrieves a slice of all the pad templates associated with this class. +// The list must not be modified. +func (e *ElementClass) GetAllPadTemplates() []*PadTemplate { + glist := C.gst_element_class_get_pad_template_list(e.Instance()) + return glistToPadTemplateSlice(glist) +} + +// SetMetadata sets the detailed information for this class. +// +// `longname` - The english long name of the element. E.g "File Sink" +// +// `classification` - A string describing the type of element, as an unordered list separated with slashes ('/'). E.g: "Sink/File" +// +// `description` - Sentence describing the purpose of the element. E.g: "Write stream to a file" +// +// `author` - Name and contact details of the author(s). Use \n to separate multiple author metadata. E.g: "Joe Bloggs " +func (e *ElementClass) SetMetadata(longname, classification, description, author string) { + C.gst_element_class_set_static_metadata( + e.Instance(), + (*C.gchar)(C.CString(longname)), + (*C.gchar)(C.CString(classification)), + (*C.gchar)(C.CString(description)), + (*C.gchar)(C.CString(author)), + ) +} diff --git a/gst/gst_errors.go b/gst/gst_errors.go new file mode 100644 index 0000000..c62aa85 --- /dev/null +++ b/gst/gst_errors.go @@ -0,0 +1,84 @@ +package gst + +/* +#include "gst.go.h" +*/ +import "C" + +// Domain represents the different types of error domains. +type Domain string + +// ErrorDomain castings +const ( + DomainCore Domain = "CORE" + DomainLibrary Domain = "LIBRARY" + DomainResource Domain = "RESOURCE" + DomainStream Domain = "STREAM" +) + +// ErrorCode represents GstGError codes. +type ErrorCode int + +// Type castings of CoreErrors +const ( + CoreErrorFailed ErrorCode = C.GST_CORE_ERROR_FAILED // (1) – a general error which doesn't fit in any other category. Make sure you add a custom message to the error call. + CoreErrorTooLazy ErrorCode = C.GST_CORE_ERROR_TOO_LAZY // (2) – do not use this except as a placeholder for deciding where to go while developing code. + CoreErrorNotImplemented ErrorCode = C.GST_CORE_ERROR_NOT_IMPLEMENTED // (3) – use this when you do not want to implement this functionality yet. + CoreErrorStateChange ErrorCode = C.GST_CORE_ERROR_STATE_CHANGE // (4) – used for state change errors. + CoreErrorPad ErrorCode = C.GST_CORE_ERROR_PAD // (5) – used for pad-related errors. + CoreErrorThread ErrorCode = C.GST_CORE_ERROR_THREAD // (6) – used for thread-related errors. + CoreErrorNegotiation ErrorCode = C.GST_CORE_ERROR_NEGOTIATION // (7) – used for negotiation-related errors. + CoreErrorEvent ErrorCode = C.GST_CORE_ERROR_EVENT // (8) – used for event-related errors. + CoreErrorSeek ErrorCode = C.GST_CORE_ERROR_SEEK // (9) – used for seek-related errors. + CoreErrorCaps ErrorCode = C.GST_CORE_ERROR_CAPS // (10) – used for caps-related errors. + CoreErrorTag ErrorCode = C.GST_CORE_ERROR_TAG // (11) – used for negotiation-related errors. + CoreErrorMissingPlugin ErrorCode = C.GST_CORE_ERROR_MISSING_PLUGIN // (12) – used if a plugin is missing. + CoreErrorClock ErrorCode = C.GST_CORE_ERROR_CLOCK // (13) – used for clock related errors. + CoreErrorDisabled ErrorCode = C.GST_CORE_ERROR_DISABLED // (14) – used if functionality has been disabled at compile time. +) + +// Type castings for LibraryErrors +const ( + LibraryErrorFailed ErrorCode = C.GST_LIBRARY_ERROR_FAILED // (1) – a general error which doesn't fit in any other category. Make sure you add a custom message to the error call. + LibraryErrorTooLazy ErrorCode = C.GST_LIBRARY_ERROR_TOO_LAZY // (2) – do not use this except as a placeholder for deciding where to go while developing code. + LibraryErrorInit ErrorCode = C.GST_LIBRARY_ERROR_INIT // (3) – used when the library could not be opened. + LibraryErrorShutdown ErrorCode = C.GST_LIBRARY_ERROR_SHUTDOWN // (4) – used when the library could not be closed. + LibraryErrorSettings ErrorCode = C.GST_LIBRARY_ERROR_SETTINGS // (5) – used when the library doesn't accept settings. + LibraryErrorEncode ErrorCode = C.GST_LIBRARY_ERROR_ENCODE // (6) – used when the library generated an encoding error. +) + +// Type castings for ResourceErrors +const ( + ResourceErrorFailed ErrorCode = C.GST_RESOURCE_ERROR_FAILED // (1) – a general error which doesn't fit in any other category. Make sure you add a custom message to the error call. + ResourceErrorTooLazy ErrorCode = C.GST_RESOURCE_ERROR_TOO_LAZY // (2) – do not use this except as a placeholder for deciding where to go while developing code. + ResourceErrorNotFound ErrorCode = C.GST_RESOURCE_ERROR_NOT_FOUND // (3) – used when the resource could not be found. + ResourceErrorBusy ErrorCode = C.GST_RESOURCE_ERROR_BUSY // (4) – used when resource is busy. + ResourceErrorOpenRead ErrorCode = C.GST_RESOURCE_ERROR_OPEN_READ // (5) – used when resource fails to open for reading. + ResourceErrorOpenWrite ErrorCode = C.GST_RESOURCE_ERROR_OPEN_WRITE // (6) – used when resource fails to open for writing. + ResourceErrorOpenReadWrite ErrorCode = C.GST_RESOURCE_ERROR_OPEN_READ_WRITE // (7) – used when resource cannot be opened for both reading and writing, or either (but unspecified which). + ResourceErrorClose ErrorCode = C.GST_RESOURCE_ERROR_CLOSE // (8) – used when the resource can't be closed. + ResourceErrorRead ErrorCode = C.GST_RESOURCE_ERROR_READ // (9) – used when the resource can't be read from. + ResourceErrorWrite ErrorCode = C.GST_RESOURCE_ERROR_WRITE // (10) – used when the resource can't be written to. + ResourceErrorSeek ErrorCode = C.GST_RESOURCE_ERROR_SEEK // (11) – used when a seek on the resource fails. + ResourceErrorSync ErrorCode = C.GST_RESOURCE_ERROR_SYNC // (12) – used when a synchronize on the resource fails. + ResourceErrorSettings ErrorCode = C.GST_RESOURCE_ERROR_SETTINGS // (13) – used when settings can't be manipulated on. + ResourceErrorNoSpaceLeft ErrorCode = C.GST_RESOURCE_ERROR_NO_SPACE_LEFT // (14) – used when the resource has no space left. + ResourceErrorNotAuthorized ErrorCode = C.GST_RESOURCE_ERROR_NOT_AUTHORIZED // (15) – used when the resource can't be opened due to missing authorization. (Since: 1.2.4) +) + +// Type castings for StreamErrors +const ( + StreamErrorFailed ErrorCode = C.GST_STREAM_ERROR_FAILED // (1) – a general error which doesn't fit in any other category. Make sure you add a custom message to the error call. + StreamErrorTooLazy ErrorCode = C.GST_STREAM_ERROR_TOO_LAZY // (2) – do not use this except as a placeholder for deciding where to go while developing code. + StreamErrorNotImplemented ErrorCode = C.GST_STREAM_ERROR_NOT_IMPLEMENTED // (3) – use this when you do not want to implement this functionality yet. + StreamErrorTypeNotFound ErrorCode = C.GST_STREAM_ERROR_TYPE_NOT_FOUND // (4) – used when the element doesn't know the stream's type. + StreamErrorWrongType ErrorCode = C.GST_STREAM_ERROR_WRONG_TYPE // (5) – used when the element doesn't handle this type of stream. + StreamErrorCodecNotFound ErrorCode = C.GST_STREAM_ERROR_CODEC_NOT_FOUND // (6) – used when there's no codec to handle the stream's type. + StreamErrorDecode ErrorCode = C.GST_STREAM_ERROR_DECODE // (7) – used when decoding fails. + StreamErrorEncode ErrorCode = C.GST_STREAM_ERROR_ENCODE // (8) – used when encoding fails. + StreamErrorDemux ErrorCode = C.GST_STREAM_ERROR_DEMUX // (9) – used when demuxing fails. + StreamErrorMux ErrorCode = C.GST_STREAM_ERROR_MUX // (10) – used when muxing fails. + StreamErrorFormat ErrorCode = C.GST_STREAM_ERROR_FORMAT // (11) – used when the stream is of the wrong format (for example, wrong caps). + StreamErrorDecrypt ErrorCode = C.GST_STREAM_ERROR_DECRYPT // (12) – used when the stream is encrypted and can't be decrypted because this is not supported by the element. + StreamErrorDecryptNoKey ErrorCode = C.GST_STREAM_ERROR_DECRYPT_NOKEY // (13) – used when the stream is encrypted and can't be decrypted because no suitable key is available. +) diff --git a/gst/gst_event.go b/gst/gst_event.go index 38d8971..fda1212 100644 --- a/gst/gst_event.go +++ b/gst/gst_event.go @@ -15,6 +15,10 @@ type Event struct { ptr *C.GstEvent } +// FromGstEventUnsafe wraps the pointer to the given C GstEvent with the go type. +// This is meant for internal usage and is exported for visibility to other packages. +func FromGstEventUnsafe(ev unsafe.Pointer) *Event { return wrapEvent((*C.GstEvent)(ev)) } + // Instance returns the underlying GstEvent instance. func (e *Event) Instance() *C.GstEvent { return C.toGstEvent(unsafe.Pointer(e.ptr)) } diff --git a/gst/gst_object.go b/gst/gst_object.go index e173553..81d72e6 100644 --- a/gst/gst_object.go +++ b/gst/gst_object.go @@ -79,12 +79,7 @@ func (o *Object) ListProperties() []*ParameterSpec { C.g_param_spec_sink(prop) // steal the ref on the property out = append(out, &ParameterSpec{ paramSpec: prop, - Name: C.GoString(C.g_param_spec_get_name(prop)), - Blurb: C.GoString(C.g_param_spec_get_blurb(prop)), - Flags: flags, - ValueType: glib.Type(prop.value_type), - OwnerType: glib.Type(prop.owner_type), - DefaultValue: glib.ValueFromNative(unsafe.Pointer(&gval)), + defaultValue: glib.ValueFromNative(unsafe.Pointer(&gval)), }) } return out diff --git a/gst/gst_plugin.go b/gst/gst_plugin.go index 49bf331..c38087b 100644 --- a/gst/gst_plugin.go +++ b/gst/gst_plugin.go @@ -1,11 +1,160 @@ package gst -// #include "gst.go.h" +/* +#include "gst.go.h" + +extern gboolean goPluginInit (GstPlugin * plugin, gpointer user_data); +extern gboolean goGlobalPluginInit (GstPlugin * plugin); + +gboolean cgoPluginInit (GstPlugin * plugin, gpointer user_data) +{ + return goPluginInit(plugin, user_data); +} + +gboolean cgoGlobalPluginInit(GstPlugin * plugin) +{ + return goGlobalPluginInit(plugin); +} + +GstPluginDesc * exportPluginMeta (gint major, gint minor, gchar * name, gchar * description, GstPluginInitFunc init, gchar * version, gchar * license, gchar * source, gchar * package, gchar * origin, gchar * release_datetime) +{ + GstPluginDesc * desc = malloc ( sizeof(GstPluginDesc) ); + + desc->major_version = major; + desc->minor_version = minor; + desc->name = name; + desc->description = description; + desc->plugin_init = init; + desc->version = version; + desc->license = license; + desc->source = source; + desc->package = package; + desc->origin = origin; + desc->release_datetime = release_datetime; + + return desc; +} + +*/ import "C" +import ( + "errors" + "unsafe" + + "github.com/gotk3/gotk3/glib" + gopointer "github.com/mattn/go-pointer" +) + +// PluginMetadata represents the information to include when registering a new plugin +// with gstreamer. +type PluginMetadata struct { + // The major version number of the GStreamer core that the plugin was compiled for, you can just use VersionMajor here + MajorVersion Version + // The minor version number of the GStreamer core that the plugin was compiled for, you can just use VersionMinor here + MinorVersion Version + // A unique name of the plugin (ideally prefixed with an application- or library-specific namespace prefix in order to + // avoid name conflicts in case a similar plugin with the same name ever gets added to GStreamer) + Name string + // A description of the plugin + Description string + // The function to call when initiliazing the plugin + Init PluginInitFunc + // The version of the plugin + Version string + // The license for the plugin, must match one of the license constants in this package + License License + // The source module the plugin belongs to + Source string + // The shipped package the plugin belongs to + Package string + // The URL to the provider of the plugin + Origin string + // The date of release in ISO 8601 format. + // See https://gstreamer.freedesktop.org/documentation/gstreamer/gstplugin.html?gi-language=c#GstPluginDesc for more details. + ReleaseDate string +} + +var globalPluginInit PluginInitFunc + +// Export will export the PluginMetadata to an unsafe pointer to a GstPluginDesc. +func (p *PluginMetadata) Export() unsafe.Pointer { + globalPluginInit = p.Init + desc := C.exportPluginMeta( + C.gint(p.MajorVersion), + C.gint(p.MinorVersion), + (*C.gchar)(C.CString(p.Name)), + (*C.gchar)(C.CString(p.Description)), + (C.GstPluginInitFunc(C.cgoGlobalPluginInit)), + (*C.gchar)(C.CString(p.Version)), + (*C.gchar)(C.CString(string(p.License))), + (*C.gchar)(C.CString(p.Source)), + (*C.gchar)(C.CString(p.Package)), + (*C.gchar)(C.CString(p.Origin)), + (*C.gchar)(C.CString(p.ReleaseDate)), + ) + return unsafe.Pointer(desc) +} + +// PluginInitFunc is a function called by the plugin loader at startup. This function should register +// all the features of the plugin. The function should return true if the plugin is initialized successfully. +type PluginInitFunc func(*Plugin) bool + // Plugin is a go representation of a GstPlugin. type Plugin struct{ *Object } +// RegisterPlugin will register a static plugin, i.e. a plugin which is private to an application +// or library and contained within the application or library (as opposed to being shipped as a +// separate module file). +func RegisterPlugin(desc *PluginMetadata, initFunc PluginInitFunc) bool { + cName := C.CString(desc.Name) + cDesc := C.CString(desc.Description) + cVers := C.CString(desc.Version) + cLics := C.CString(string(desc.License)) + cSrc := C.CString(desc.Source) + cPkg := C.CString(desc.Package) + cOrg := C.CString(desc.Origin) + defer func() { + for _, ptr := range []*C.char{cName, cDesc, cVers, cLics, cSrc, cPkg, cOrg} { + C.free(unsafe.Pointer(ptr)) + } + }() + fPtr := gopointer.Save(initFunc) + return gobool(C.gst_plugin_register_static_full( + C.gint(desc.MajorVersion), C.gint(desc.MinorVersion), + (*C.gchar)(cName), (*C.gchar)(cDesc), + C.GstPluginInitFullFunc(C.cgoPluginInit), + (*C.gchar)(cVers), (*C.gchar)(cLics), + (*C.gchar)(cSrc), (*C.gchar)(cPkg), + (*C.gchar)(cOrg), (C.gpointer)(unsafe.Pointer(fPtr)), + )) +} + +// LoadPluginByName loads the named plugin and places a ref count on it. The function +// returns nil if the plugin could not be loaded. +func LoadPluginByName(name string) *Plugin { + cstr := C.CString(name) + defer C.free(unsafe.Pointer(cstr)) + plugin := C.gst_plugin_load_by_name((*C.gchar)(unsafe.Pointer(cstr))) + if plugin == nil { + return nil + } + return wrapPlugin(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(plugin))}) +} + +// LoadPluginFile loads the given plugin and refs it. If an error is returned Plugin will be nil. +func LoadPluginFile(fpath string) (*Plugin, error) { + cstr := C.CString(fpath) + defer C.free(unsafe.Pointer(cstr)) + var gerr *C.GError + plugin := C.gst_plugin_load_file((*C.gchar)(unsafe.Pointer(cstr)), (**C.GError)(&gerr)) + if gerr != nil { + defer C.g_free((C.gpointer)(gerr)) + return nil, errors.New(C.GoString(gerr.message)) + } + return wrapPlugin(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(plugin))}), nil +} + // Instance returns the underlying GstPlugin instance. func (p *Plugin) Instance() *C.GstPlugin { return C.toGstPlugin(p.Unsafe()) } @@ -37,12 +186,12 @@ func (p *Plugin) Version() string { } // License returns the license for this plugin. -func (p *Plugin) License() string { +func (p *Plugin) License() License { ret := C.gst_plugin_get_license((*C.GstPlugin)(p.Instance())) if ret == nil { return "" } - return C.GoString(ret) + return License(C.GoString(ret)) } // Source returns the source module for this plugin. diff --git a/gst/gst_query.go b/gst/gst_query.go index 3295cb5..5bde9cc 100644 --- a/gst/gst_query.go +++ b/gst/gst_query.go @@ -14,6 +14,10 @@ type Query struct { ptr *C.GstQuery } +// FromGstQueryUnsafe wraps the pointer to the given C GstQuery with the go type. +// This is meant for internal usage and is exported for visibility to other packages. +func FromGstQueryUnsafe(query unsafe.Pointer) *Query { return wrapQuery((*C.GstQuery)(query)) } + // NewAcceptCapsQuery constructs a new query object for querying if caps are accepted. func NewAcceptCapsQuery(caps *Caps) *Query { return wrapQuery(C.gst_query_new_accept_caps(caps.Instance())) diff --git a/gst/gst_segment.go b/gst/gst_segment.go index 3f05941..5fc587a 100644 --- a/gst/gst_segment.go +++ b/gst/gst_segment.go @@ -2,6 +2,7 @@ package gst // #include "gst.go.h" import "C" +import "unsafe" // Segment is a go wrapper around a GstSegment. // See: https://gstreamer.freedesktop.org/documentation/gstreamer/gstsegment.html?gi-language=c#GstSegment @@ -9,6 +10,12 @@ type Segment struct { ptr *C.GstSegment } +// FromGstSegmentUnsafe wraps the given C GstSegment in the go type. It is meant for internal usage +// and exported for visibilty to other packages. +func FromGstSegmentUnsafe(segment unsafe.Pointer) *Segment { + return wrapSegment((*C.GstSegment)(segment)) +} + // NewSegment allocates and initializes a new Segment. func NewSegment() *Segment { return wrapSegment(C.gst_segment_new()) diff --git a/gst/gst_uri_handler.go b/gst/gst_uri_handler.go index 8004d4d..6543e26 100644 --- a/gst/gst_uri_handler.go +++ b/gst/gst_uri_handler.go @@ -1,6 +1,8 @@ package gst -// #include "gst.go.h" +/* +#include "gst.go.h" +*/ import "C" import ( "errors" @@ -10,7 +12,8 @@ import ( ) // InterfaceURIHandler represents the GstURIHandler interface GType. Use this when querying bins -// for elements that implement a URIHandler. +// for elements that implement a URIHandler, or when signaling that a GoElement provides this +// interface. var InterfaceURIHandler = glib.Type(C.GST_TYPE_URI_HANDLER) // URIHandler represents an interface that elements can implement to provide URI handling @@ -20,13 +23,13 @@ type URIHandler interface { GetURI() string // GetURIType returns the type of URI this element can handle. GetURIType() URIType - // GetURIProtocols returns the protocols this element can handle. - GetURIProtocols() []string + // GetProtocols returns the protocols this element can handle. + GetProtocols() []string // SetURI tries to set the URI of the given handler. SetURI(string) (bool, error) } -// gstURIHandler implements a URIHandler that is backed by an Element from the C runtime. +// gstURIHandler implements a URIHandler that is backed by an Element from the C API. type gstURIHandler struct { ptr *C.GstElement } @@ -48,8 +51,8 @@ func (g *gstURIHandler) GetURIType() URIType { return URIType(ty) } -// GetURIProtocols returns the protocols this element can handle. -func (g *gstURIHandler) GetURIProtocols() []string { +// GetProtocols returns the protocols this element can handle. +func (g *gstURIHandler) GetProtocols() []string { protocols := C.gst_uri_handler_get_protocols((*C.GstURIHandler)(g.Instance())) if protocols == nil { return nil diff --git a/gst/gst_wrappers.go b/gst/gst_wrappers.go index 4ddd489..6c0aab4 100644 --- a/gst/gst_wrappers.go +++ b/gst/gst_wrappers.go @@ -209,6 +209,10 @@ func wrapAllocationParams(obj *C.GstAllocationParams) *AllocationParams { return &AllocationParams{ptr: obj} } +func wrapElementClass(klass C.gpointer) *ElementClass { + return &ElementClass{&ObjectClass{ptr: C.toGObjectClass(unsafe.Pointer(klass))}} +} + // Clock wrappers func clockTimeToDuration(n ClockTime) time.Duration { diff --git a/gst/interfaces.go b/gst/interfaces.go new file mode 100644 index 0000000..9ee2857 --- /dev/null +++ b/gst/interfaces.go @@ -0,0 +1,158 @@ +package gst + +/* +#include "gst.go.h" + +extern void goClassInit (gpointer g_class, gpointer class_data); +extern void goInstanceInit (GTypeInstance * instance, gpointer g_class); + +extern void goObjectSetProperty (GObject * object, guint property_id, const GValue * value, GParamSpec *pspec); +extern void goObjectGetProperty (GObject * object, guint property_id, GValue * value, GParamSpec * pspec); +extern void goObjectConstructed (GObject * object); +extern void goObjectFinalize (GObject * object, gpointer klass); + +void objectFinalize (GObject * object) +{ + GObjectClass *parent = g_type_class_peek_parent((G_OBJECT_GET_CLASS(object))); + goObjectFinalize(object, G_OBJECT_GET_CLASS(object)); + parent->finalize(object); +} + +void objectConstructed (GObject * object) +{ + GObjectClass *parent = g_type_class_peek_parent((G_OBJECT_GET_CLASS(object))); + goObjectConstructed(object); + parent->constructed(object); +} + +void cgoClassInit (gpointer g_class, gpointer class_data) +{ + ((GObjectClass *)g_class)->set_property = goObjectSetProperty; + ((GObjectClass *)g_class)->get_property = goObjectGetProperty; + ((GObjectClass *)g_class)->constructed = objectConstructed; + ((GObjectClass *)g_class)->finalize = objectFinalize; + + goClassInit(g_class, class_data); +} + +void cgoInstanceInit (GTypeInstance * instance, gpointer g_class) +{ + goInstanceInit(instance, g_class); +} + +*/ +import "C" + +import ( + "reflect" + "unsafe" + + "github.com/gotk3/gotk3/glib" + gopointer "github.com/mattn/go-pointer" +) + +// Extendable is an interface implemented by extendable classes. It provides +// the methods necessary to setup the vmethods on the object it represents. +type Extendable interface { + Type() glib.Type + ClassSize() int64 + InstanceSize() int64 + InitClass(unsafe.Pointer, GoElement) +} + +type extendElement struct{} + +func (e *extendElement) Type() glib.Type { return glib.Type(C.gst_element_get_type()) } +func (e *extendElement) ClassSize() int64 { return int64(C.sizeof_GstElementClass) } +func (e *extendElement) InstanceSize() int64 { return int64(C.sizeof_GstElement) } +func (e *extendElement) InitClass(klass unsafe.Pointer, elem GoElement) {} + +// ExtendsElement signifies a GoElement that extends a GstElement. +var ExtendsElement Extendable = &extendElement{} + +// GoElement is an interface to be implemented by GStreamer elements built using the +// go bindings. The various methods are called throughout the lifecycle of the plugin. +type GoElement interface { + GoObjectSubclass + GoObject +} + +// privateFromObj returns the actual value of the address we stored in the object's private data. +func privateFromObj(obj unsafe.Pointer) unsafe.Pointer { + private := C.g_type_instance_get_private((*C.GTypeInstance)(obj), C.objectGType((*C.GObject)(obj))) + privAddr := (*unsafe.Pointer)(unsafe.Pointer(private)) + return *privAddr +} + +// FromObjectUnsafePrivate will return the GoElement addressed in the private data of the given GObject. +func FromObjectUnsafePrivate(obj unsafe.Pointer) GoElement { + ptr := gopointer.Restore(privateFromObj(obj)) + return ptr.(GoElement) +} + +// GoObjectSubclass is an interface that abstracts on the GObjectClass. It should be implemented +// by plugins using the go bindings. +type GoObjectSubclass interface { + // New should return a new instantiated GoElement ready to be used. + New() GoElement + // TypeInit is called after the GType is registered and right before ClassInit. It is when the + // element should add any interfaces it plans to implement. + TypeInit(*TypeInstance) + // ClassInit is called on the element after registering it with GStreamer. This is when the element + // should install any properties and pad templates it has. + ClassInit(*ElementClass) +} + +// GoObject is an interface that abstracts on the GObject. It should be implemented by plugins using +// the gobindings. +type GoObject interface { + // SetProperty should set the value of the property with the given id. ID is the index+1 of the parameter + // in the order it was registered. + SetProperty(obj *Object, id uint, value *glib.Value) + // GetProperty should retrieve the value of the property with the given id. ID is the index+1 of the parameter + // in the order it was registered. + GetProperty(obj *Object, id uint) *glib.Value + // Constructed is called when the Object has finished setting up. + Constructed(*Object) +} + +type classData struct { + elem GoElement + ext Extendable +} + +func gtypeForGoElement(name string, elem GoElement, extendable Extendable) C.GType { + registerMutex.Lock() + defer registerMutex.Unlock() + // fmt.Printf("Checking registration of %v\n", reflect.TypeOf(elem).String()) + if registered, ok := registeredTypes[reflect.TypeOf(elem).String()]; ok { + return registered + } + classData := &classData{ + elem: elem, + ext: extendable, + } + ptr := gopointer.Save(classData) + typeInfo := C.GTypeInfo{ + class_size: C.gushort(extendable.ClassSize()), + base_init: nil, + base_finalize: nil, + class_init: C.GClassInitFunc(C.cgoClassInit), + class_finalize: nil, + class_data: (C.gconstpointer)(ptr), + instance_size: C.gushort(extendable.InstanceSize()), + n_preallocs: 0, + instance_init: C.GInstanceInitFunc(C.cgoInstanceInit), + value_table: nil, + } + gtype := C.g_type_register_static( + C.GType(extendable.Type()), + (*C.gchar)(C.CString(name)), + &typeInfo, + C.GTypeFlags(0), + ) + elem.TypeInit(&TypeInstance{gtype: gtype, gotype: elem}) + // fmt.Printf("Registering %v to type %v\n", reflect.TypeOf(elem).String(), gtype) + registeredTypes[reflect.TypeOf(elem).String()] = gtype + return gtype +} diff --git a/gst/register.go b/gst/register.go new file mode 100644 index 0000000..a1e1aca --- /dev/null +++ b/gst/register.go @@ -0,0 +1,11 @@ +package gst + +// #include "gst.go.h" +import "C" +import "sync" + +var registerMutex sync.RWMutex + +var registeredTypes = make(map[string]C.GType) +var registeredClasses = make(map[C.gpointer]GoElement) +var globalURIHdlr URIHandler