add tag/toc examples and general cleanup

This commit is contained in:
tinyzimmer
2020-10-05 14:23:46 +03:00
parent 9335dddc6e
commit 0c346775a3
11 changed files with 412 additions and 33 deletions

View File

@@ -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()

View File

@@ -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)
}

171
examples/toc/main.go Normal file
View File

@@ -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 <file>")
}
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)
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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(), &gtoc, &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) {

View File

@@ -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 {

View File

@@ -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 {

76
gst/gst_uri_handler.go Normal file
View File

@@ -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
}