diff --git a/examples/inspect/main.go b/examples/inspect/main.go index 3d12821..1d5938c 100644 --- a/examples/inspect/main.go +++ b/examples/inspect/main.go @@ -243,10 +243,12 @@ func printURIHandlerInfo(elem *gst.Element) { fmt.Println() } - colorOrange.print("URI handling capabilities:\n") - colorLightGray.printfIndent(2, "Element can act as %s.\n", strings.ToLower(elem.GetURIType().String())) + uriHandler := elem.URIHandler() - protos := elem.GetURIProtocols() + 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() diff --git a/examples/tagsetter/main.go b/examples/tagsetter/main.go new file mode 100644 index 0000000..9476378 --- /dev/null +++ b/examples/tagsetter/main.go @@ -0,0 +1,97 @@ +// This example demonstrates how to set and store metadata using GStreamer. +// +// Some elements support setting tags on a media stream. An example would be +// id3v2mux. The element signals this by implementing The GstTagsetter interface. +// You can query any element implementing this interface from the pipeline, and +// then tell the returned implementation of GstTagsetter what tags to apply to +// the media stream. +// +// This example's pipeline creates a new flac file from the testaudiosrc +// that the example application will add tags to using GstTagsetter. +// The operated pipeline looks like this: +// +// {audiotestsrc} - {flacenc} - {filesink} +// +// For example for pipelines that transcode a multimedia file, the input +// already has tags. For cases like this, the GstTagsetter has the merge +// setting, which the application can configure to tell the element +// implementing the interface whether to merge newly applied tags to the +// already existing ones, or if all existing ones should replace, etc. +// (More modes of operation are possible, see: gst.TagMergeMode) +// This merge-mode can also be supplied to any method that adds new tags. +package main + +import ( + "fmt" + "time" + + "github.com/tinyzimmer/go-gst/examples" + "github.com/tinyzimmer/go-gst/gst" +) + +func tagsetter() error { + gst.Init(nil) + + pipeline, err := gst.NewPipelineFromString( + "audiotestsrc wave=white-noise num-buffers=10000 ! flacenc ! filesink location=test.flac", + ) + if err != nil { + return err + } + defer pipeline.Destroy() + + // Query the pipeline for elements implementing the GstTagsetter interface. + // In our case, this will return the flacenc element. + element, err := pipeline.GetByInterface(gst.InterfaceTagSetter) + if err != nil { + return err + } + + // We actually just retrieved a *gst.Element with the above call. We can retrieve + // the underying TagSetter interface like this. + tagsetter := element.TagSetter() + + // Tell the element implementing the GstTagsetter interface how to handle already existing + // metadata. + tagsetter.SetTagMergeMode(gst.TagMergeKeepAll) + + // Set the "title" tag to "Special randomized white-noise". + // + // The first parameter gst.TagMergeAppend tells the tagsetter to append this title + // if there already is one. + tagsetter.AddTagValue(gst.TagMergeAppend, gst.TagTitle, "Special randomized white-noise") + + pipeline.SetState(gst.StatePlaying) + + var cont bool + var pipelineErr error + for { + msg := pipeline.GetPipelineBus().TimedPop(time.Duration(-1)) + if msg == nil { + break + } + if cont, pipelineErr = handleMessage(msg); pipelineErr != nil || !cont { + pipeline.SetState(gst.StateNull) + break + } + } + + return pipelineErr +} + +func handleMessage(msg *gst.Message) (bool, error) { + defer msg.Unref() + switch msg.Type() { + case gst.MessageTag: + fmt.Println(msg) // Prirnt our tags + case gst.MessageEOS: + return false, nil + case gst.MessageError: + return false, msg.ParseError() + } + return true, nil +} + +func main() { + examples.Run(tagsetter) +} diff --git a/examples/toc/main.go b/examples/toc/main.go new file mode 100644 index 0000000..311d29b --- /dev/null +++ b/examples/toc/main.go @@ -0,0 +1,171 @@ +// This example demonstrates the use of GStreamer's ToC API. +// +// This API is used to manage a table of contents contained in the handled media stream. +// Chapters within a matroska file would be an example of a scenario for using +// this API. Elements that can parse ToCs from a stream (such as matroskademux) +// notify all elements in the pipeline when they encountered a ToC. +// For this, the example operates the following pipeline: +// +// /-{queue} - {fakesink} +// {filesrc} - {decodebin} - {queue} - {fakesink} +// \- ... +// +package main + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/tinyzimmer/go-gst/examples" + "github.com/tinyzimmer/go-gst/gst" +) + +func tagsetter(mainLoop *gst.MainLoop) error { + gst.Init(nil) + + if len(os.Args) < 2 { + return errors.New("Usage: toc ") + } + + pipeline, err := gst.NewPipeline("") + if err != nil { + return err + } + defer pipeline.Destroy() + + src, err := gst.NewElement("filesrc") + if err != nil { + return err + } + decodebin, err := gst.NewElement("decodebin") + if err != nil { + return err + } + + src.SetProperty("location", os.Args[1]) + + pipeline.AddMany(src, decodebin) + gst.ElementLinkMany(src, decodebin) + + // Connect to decodebin's pad-added signal, that is emitted whenever it found another stream + // from the input file and found a way to decode it to its raw format. + decodebin.Connect("pad-added", func(_ *gst.Element, srcPad *gst.Pad) { + + // In this example, we are only interested about parsing the ToC, so + // we simply pipe every encountered stream into a fakesink, essentially + // throwing away the data. + elems, err := gst.NewElementMany("queue", "fakesink") + if err != nil { + fmt.Println("Could not create decodebin pipeline") + return + } + pipeline.AddMany(elems...) + gst.ElementLinkMany(elems...) + for _, e := range elems { + e.SyncStateWithParent() + } + + queue := elems[0] + sinkPad := queue.GetStaticPad("sink") + if sinkPad == nil { + fmt.Println("Could not get static pad from sink") + return + } + + srcPad. + Link(sinkPad) + }) + + if err := pipeline.SetState(gst.StatePaused); err != nil { + return err + } + + // Instead of using the main loop, we manually iterate over GStreamer's bus messages + // in this example. We don't need any special functionality like timeouts or GLib socket + // notifications, so this is sufficient. The bus is manually operated by repeatedly calling + // timed_pop on the bus with the desired timeout for when to stop waiting for new messages. + // (-1 = Wait forever) + for { + msg := pipeline.GetPipelineBus().TimedPop(time.Duration(-1)) + switch msg.Type() { + + // When we use this method of popping from the bus (instead of a Watch), we own a + // reference to every message received (this may be abstracted later). + default: + // fmt.Println(msg) + msg.Unref() + + // End of stream + case gst.MessageEOS: + msg.Unref() + break + + // Errors from any elements + case gst.MessageError: + gerr := msg.ParseError() + if debug := gerr.DebugString(); debug != "" { + fmt.Println("go-gst-debug:", debug) + } + msg.Unref() + return gerr + + // Some element found a ToC in the current media stream and told + // us by posting a message to GStreamer's bus. + case gst.MessageTOC: + // Parse the toc from the message + toc, updated := msg.ParseTOC() + msg.Unref() + fmt.Printf("Received toc: %s - updated %v\n", toc.GetScope(), updated) + // Get a list of tags that are ToC specific. + if tags := toc.GetTags(); tags != nil { + fmt.Println("- tags:", tags) + } + // ToCs do not have a fixed structure. Depending on the format that + // they were parsed from, they might have different tree-like structures, + // so applications that want to support ToCs (for example in the form + // of jumping between chapters in a video) have to try parsing and + // interpreting the ToC manually. + // In this example, we simply want to print the ToC structure, so + // we iterate everything and don't try to interpret anything. + for _, entry := range toc.GetEntries() { + // Every entry in a ToC has its own type. One type could for + // example be Chapter. + fmt.Printf("\t%s - %s\n", entry.GetEntryTypeString(), entry.GetUID()) + + // Every ToC entry can have a set of timestamps (start, stop). + if ok, start, stop := entry.GetStartStopTimes(); ok { + startDur := time.Duration(start) * time.Nanosecond + stopDur := time.Duration(stop) * time.Nanosecond + fmt.Printf("\t- start: %s, stop: %s\n", startDur, stopDur) + } + + // Every ToC entry can have tags to it. + if tags := entry.GetTags(); tags != nil { + fmt.Println("\t- tags:", tags) + } + + // Every ToC entry can have a set of child entries. + // With this structure, you can create trees of arbitrary depth. + for _, subEntry := range entry.GetSubEntries() { + fmt.Printf("\n\t\t%s - %s\n", subEntry.GetEntryTypeString(), subEntry.GetUID()) + if ok, start, stop := entry.GetStartStopTimes(); ok { + startDur := time.Duration(start) * time.Nanosecond + stopDur := time.Duration(stop) * time.Nanosecond + fmt.Printf("\t\t- start: %s, stop: %s\n", startDur, stopDur) + } + if tags := entry.GetTags(); tags != nil { + fmt.Println("\t\t- tags:", tags) + } + } + } + + toc.Unref() + } + } +} + +func main() { + examples.RunLoop(tagsetter) +} diff --git a/gst/constants.go b/gst/constants.go index f8f3820..fddd73c 100644 --- a/gst/constants.go +++ b/gst/constants.go @@ -806,6 +806,17 @@ const ( TOCScopeCurrent TOCScope = C.GST_TOC_SCOPE_CURRENT ) +// String implements a stringer on a TOCScope +func (t TOCScope) String() string { + switch t { + case TOCScopeGlobal: + return "global" + case TOCScopeCurrent: + return "current" + } + return "" +} + // TOCLoopType represents a GstTocLoopType type TOCLoopType int diff --git a/gst/gst_bin.go b/gst/gst_bin.go index 2780697..c122405 100644 --- a/gst/gst_bin.go +++ b/gst/gst_bin.go @@ -91,6 +91,26 @@ func (b *Bin) GetElementsSorted() ([]*Element, error) { return iteratorToElementSlice(iterator) } +// GetByInterface looks for an element inside the bin that implements the given interface. If such an +// element is found, it returns the element. You can cast this element to the given interface afterwards. +// If you want all elements that implement the interface, use GetAllByInterface. This function recurses +// into child bins. +func (b *Bin) GetByInterface(iface glib.Type) (*Element, error) { + elem := C.gst_bin_get_by_interface(b.Instance(), C.GType(iface)) + if elem == nil { + return nil, fmt.Errorf("Could not find any elements implementing %s", iface.Name()) + } + return wrapElement(toGObject(unsafe.Pointer(elem))), nil +} + +// GetAllByInterface looks for all elements inside the bin that implements the given interface. You can +// safely cast all returned elements to the given interface. The function recurses inside child bins. +// The function will return a series of Elements that should be unreffed after use. +func (b *Bin) GetAllByInterface(iface glib.Type) ([]*Element, error) { + iterator := C.gst_bin_iterate_all_by_interface(b.Instance(), C.GType(iface)) + return iteratorToElementSlice(iterator) +} + // // GetElementsByFactoryName returns a list of the elements in this bin created from the given factory // // name. // func (b *Bin) GetElementsByFactoryName(name string) ([]*Element, error) { diff --git a/gst/gst_element.go b/gst/gst_element.go index 1f0056a..ee6f8e3 100644 --- a/gst/gst_element.go +++ b/gst/gst_element.go @@ -174,30 +174,14 @@ func (e *Element) IsURIHandler() bool { return gobool(C.gstElementIsURIHandler(e.Instance())) } -// TODO: Go back over URI and implement as interface - -func (e *Element) uriHandler() *C.GstURIHandler { return C.toGstURIHandler(e.Unsafe()) } - -// GetURIType returns the type of URI this element can handle. -func (e *Element) GetURIType() URIType { - if !e.IsURIHandler() { - return URIUnknown - } - ty := C.gst_uri_handler_get_uri_type((*C.GstURIHandler)(e.uriHandler())) - return URIType(ty) -} - -// GetURIProtocols returns the protocols this element can handle. -func (e *Element) GetURIProtocols() []string { - if !e.IsURIHandler() { +// 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 } - protocols := C.gst_uri_handler_get_protocols((*C.GstURIHandler)(e.uriHandler())) - if protocols == nil { - return nil - } - size := C.sizeOfGCharArray(protocols) - return goStrings(size, protocols) + return &gstURIHandler{ptr: e.Instance()} } // TOCSetter returns a TOCSetter interface if implemented by this element. Otherwise it diff --git a/gst/gst_event.go b/gst/gst_event.go index b78389c..841235a 100644 --- a/gst/gst_event.go +++ b/gst/gst_event.go @@ -205,14 +205,14 @@ func (e *Event) ParseSegmentDone() (Format, int64) { // ParseSelectStreams parses the SELECT_STREAMS event and retrieve the contained streams. func (e *Event) ParseSelectStreams() []*Stream { - outList := &C.GList{} + var outList *C.GList C.gst_event_parse_select_streams(e.Instance(), &outList) return glistToStreamSlice(outList) } // ParseSinkMessage parses the sink-message event. Unref msg after usage. func (e *Event) ParseSinkMessage() *Message { - msg := &C.GstMessage{} + var msg *C.GstMessage C.gst_event_parse_sink_message(e.Instance(), &msg) return wrapMessage(msg) } @@ -229,7 +229,7 @@ func (e *Event) ParseStep() (format Format, amount uint64, rate float64, flush, // ParseStream parses a stream-start event and extract the GstStream from it. func (e *Event) ParseStream() *Stream { - stream := &C.GstStream{} + var stream *C.GstStream C.gst_event_parse_stream(e.Instance(), &stream) return wrapStream(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(stream))}) } @@ -265,14 +265,14 @@ func (e *Event) ParseStreamStart() string { // ParseTag parses a tag event and stores the results in the given taglist location. Do not modify or free the returned // tag list. func (e *Event) ParseTag() *TagList { - out := &C.GstTagList{} + var out *C.GstTagList C.gst_event_parse_tag(e.Instance(), &out) return wrapTagList(out) } // ParseTOC parses a TOC event and store the results in the given toc and updated locations. func (e *Event) ParseTOC() (toc *TOC, updated bool) { - out := &C.GstToc{} + var out *C.GstToc var gupdated C.gboolean C.gst_event_parse_toc(e.Instance(), &out, &gupdated) return wrapTOC(out), gobool(gupdated) diff --git a/gst/gst_message.go b/gst/gst_message.go index c8f633c..7319757 100644 --- a/gst/gst_message.go +++ b/gst/gst_message.go @@ -144,6 +144,15 @@ func (m *Message) ParseTags() *TagList { return wrapTagList(tagList) } +// ParseTOC extracts the TOC from the GstMessage. The TOC returned in the output argument is +// a copy; the caller must free it with Unref when done. +func (m *Message) ParseTOC() (toc *TOC, updated bool) { + var gtoc *C.GstToc + var gupdated C.gboolean + C.gst_message_parse_toc(m.Instance(), >oc, &gupdated) + return wrapTOC(gtoc), gobool(gupdated) +} + // ParseStreamStatus parses the stream status type of the message as well as the element // that produced it. The element returned should NOT be unrefed. func (m *Message) ParseStreamStatus() (StreamStatusType, *Element) { diff --git a/gst/gst_tag_setter.go b/gst/gst_tag_setter.go index e480823..346283f 100644 --- a/gst/gst_tag_setter.go +++ b/gst/gst_tag_setter.go @@ -8,13 +8,17 @@ import ( "github.com/gotk3/gotk3/glib" ) +// InterfaceTagSetter represents the GstTagsetter interface GType. Use this when querying bins +// for elements that implement a TagSetter. +var InterfaceTagSetter = glib.Type(C.GST_TYPE_TAG_SETTER) + // TagSetter is an interface that elements can implement to provide Tag writing capabilities. type TagSetter interface { // Returns the current list of tags the setter uses. The list should not be modified or freed. GetTagList() *TagList // Adds the given tag/value pair using the given merge mode. If the tag value cannot be coerced // to a GValue when dealing with C elements, nothing will happen. - AddTagValue(mergeMode TagMergeMode, tagKey string, tagValue interface{}) + AddTagValue(mergeMode TagMergeMode, tagKey Tag, tagValue interface{}) // Merges a tag list with the given merge mode MergeTags(*TagList, TagMergeMode) // Resets the internal tag list. Elements should call this from within the state-change handler. @@ -42,8 +46,8 @@ func (t *gstTagSetter) GetTagList() *TagList { return wrapTagList(tagList) } -func (t *gstTagSetter) AddTagValue(mergeMode TagMergeMode, tagKey string, tagValue interface{}) { - ckey := C.CString(tagKey) +func (t *gstTagSetter) AddTagValue(mergeMode TagMergeMode, tagKey Tag, tagValue interface{}) { + ckey := C.CString(string(tagKey)) defer C.free(unsafe.Pointer(ckey)) gVal, err := glib.GValue(tagValue) if err != nil { diff --git a/gst/gst_toc_setter.go b/gst/gst_toc_setter.go index 7233f41..1ec432d 100644 --- a/gst/gst_toc_setter.go +++ b/gst/gst_toc_setter.go @@ -2,6 +2,11 @@ package gst // #include "gst.go.h" import "C" +import "github.com/gotk3/gotk3/glib" + +// InterfaceTOCSetter represents the GstTocSetter interface GType. Use this when querying bins +// for elements that implement a TOCSetter. +var InterfaceTOCSetter = glib.Type(C.GST_TYPE_TOC_SETTER) // TOCSetter is an interface that elements can implement to provide TOC writing capabilities. type TOCSetter interface { diff --git a/gst/gst_uri_handler.go b/gst/gst_uri_handler.go new file mode 100644 index 0000000..8004d4d --- /dev/null +++ b/gst/gst_uri_handler.go @@ -0,0 +1,76 @@ +package gst + +// #include "gst.go.h" +import "C" +import ( + "errors" + "unsafe" + + "github.com/gotk3/gotk3/glib" +) + +// InterfaceURIHandler represents the GstURIHandler interface GType. Use this when querying bins +// for elements that implement a URIHandler. +var InterfaceURIHandler = glib.Type(C.GST_TYPE_URI_HANDLER) + +// URIHandler represents an interface that elements can implement to provide URI handling +// capabilities. +type URIHandler interface { + // GetURI gets the currently handled URI. + GetURI() string + // GetURIType returns the type of URI this element can handle. + GetURIType() URIType + // GetURIProtocols returns the protocols this element can handle. + GetURIProtocols() []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. +type gstURIHandler struct { + ptr *C.GstElement +} + +func (g *gstURIHandler) Instance() *C.GstURIHandler { + return C.toGstURIHandler(unsafe.Pointer(g.ptr)) +} + +// GetURI gets the currently handled URI. +func (g *gstURIHandler) GetURI() string { + ret := C.gst_uri_handler_get_uri(g.Instance()) + defer C.g_free((C.gpointer)(unsafe.Pointer(ret))) + return C.GoString(ret) +} + +// GetURIType returns the type of URI this element can handle. +func (g *gstURIHandler) GetURIType() URIType { + ty := C.gst_uri_handler_get_uri_type((*C.GstURIHandler)(g.Instance())) + return URIType(ty) +} + +// GetURIProtocols returns the protocols this element can handle. +func (g *gstURIHandler) GetURIProtocols() []string { + protocols := C.gst_uri_handler_get_protocols((*C.GstURIHandler)(g.Instance())) + if protocols == nil { + return nil + } + size := C.sizeOfGCharArray(protocols) + return goStrings(size, protocols) +} + +// SetURI tries to set the URI of the given handler. +func (g *gstURIHandler) SetURI(uri string) (bool, error) { + curi := C.CString(uri) + defer C.free(unsafe.Pointer(curi)) + var gerr *C.GError + ret := C.gst_uri_handler_set_uri( + g.Instance(), + (*C.gchar)(unsafe.Pointer(curi)), + &gerr, + ) + if gerr != nil { + defer C.g_error_free(gerr) + return gobool(ret), errors.New(C.GoString(gerr.message)) + } + return gobool(ret), nil +}