diff --git a/examples/playbin/main.go b/examples/playbin/main.go new file mode 100644 index 0000000..90eb84f --- /dev/null +++ b/examples/playbin/main.go @@ -0,0 +1,92 @@ +// This example demonstrates GStreamer's playbin element. +// +// This element takes an arbitrary URI as parameter, and if there is a source +// element within gstreamer, that supports this uri, the playbin will try +// to automatically create a pipeline that properly plays this media source. +// For this, the playbin internally relies on more bin elements, like the +// autovideosink and the decodebin. +// Essentially, this element is a single-element pipeline able to play +// any format from any uri-addressable source that gstreamer supports. +// Much of the playbin's behavior can be controlled by so-called flags, as well +// as the playbin's properties and signals. +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/tinyzimmer/go-gst/examples" + "github.com/tinyzimmer/go-gst/gst" +) + +var srcURI string + +func playbin(mainLoop *gst.MainLoop) error { + if len(os.Args) < 2 { + return errors.New("Usage: playbin ") + } + + gst.Init(nil) + + // Create a new playbin and set the URI on it + playbin, err := gst.NewElement("playbin") + if err != nil { + return err + } + playbin.Set("uri", os.Args[1]) + + // The playbin element itself is a pipeline, so it can be used as one, despite being + // created from an element factory. + bus := playbin.GetBus() + + playbin.SetState(gst.StatePlaying) + + bus.AddWatch(func(msg *gst.Message) bool { + switch msg.Type() { + case gst.MessageEOS: + mainLoop.Quit() + return false + case gst.MessageError: + err := msg.ParseError() + fmt.Println("ERROR:", err.Error()) + if debug := err.DebugString(); debug != "" { + fmt.Println("DEBUG") + fmt.Println(debug) + } + mainLoop.Quit() + return false + // Watch state change events + case gst.MessageStateChanged: + if _, newState := msg.ParseStateChanged(); newState == gst.StatePlaying { + bin := gst.BinFromElement(playbin) + // Generate a dot graph of the pipeline to GST_DEBUG_DUMP_DOT_DIR if defined + bin.DebugBinToDotFile(gst.DebugGraphShowAll, "PLAYING") + } + + // Tag messages contain changes to tags on the stream. This can include metadata about + // the stream such as codecs, artists, albums, etc. + case gst.MessageTag: + tags := msg.ParseTags() + fmt.Println("Tags:") + if artist, ok := tags.GetString(gst.TagArtist); ok { + fmt.Println(" Artist:", artist) + } + if album, ok := tags.GetString(gst.TagAlbum); ok { + fmt.Println(" Album:", album) + } + if title, ok := tags.GetString(gst.TagTitle); ok { + fmt.Println(" Title:", title) + } + } + return true + }) + + mainLoop.Run() + + return playbin.SetState(gst.StateNull) +} + +func main() { + examples.RunLoop(playbin) +} diff --git a/gst/constants.go b/gst/constants.go index 3698641..f8f3820 100644 --- a/gst/constants.go +++ b/gst/constants.go @@ -196,6 +196,9 @@ const ( EventTypeCustomBothOOB EventType = C.GST_EVENT_CUSTOM_BOTH_OOB // (81923) – Custom upstream or downstream out-of-band event. ) +// String implements a stringer on event types +func (e EventType) String() string { return C.GoString(C.gst_event_type_get_name(C.GstEventType(e))) } + // EventTypeFlags casts GstEventTypeFlags type EventTypeFlags int diff --git a/gst/gst_bin.go b/gst/gst_bin.go index 45bf0f7..d35152e 100644 --- a/gst/gst_bin.go +++ b/gst/gst_bin.go @@ -22,6 +22,16 @@ func NewBin(name string) *Bin { return wrapBin(&glib.Object{GObject: glib.ToGObject(unsafe.Pointer(bin))}) } +// BinFromElement wraps the given Element in a Bin reference. This only works for elements +// that implement their own Bin, such as playbin. If the provided element does not implement +// a Bin then nil is returned. +func BinFromElement(elem *Element) *Bin { + if C.toGstBin(elem.Unsafe()) == nil { + return nil + } + return &Bin{elem} +} + // Instance returns the underlying GstBin instance. func (b *Bin) Instance() *C.GstBin { return C.toGstBin(b.Unsafe()) } @@ -81,6 +91,15 @@ func (b *Bin) GetElementsSorted() ([]*Element, error) { 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) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + iterator := C.gst_bin_iterate_all_by_element_factory_name(b.Instance(), (*C.gchar)(unsafe.Pointer(cname))) + return iteratorToElementSlice(iterator) +} + // Add adds an element to the bin. func (b *Bin) Add(elem *Element) error { if ok := C.gst_bin_add((*C.GstBin)(b.Instance()), (*C.GstElement)(elem.Instance())); !gobool(ok) { @@ -157,6 +176,46 @@ func (b *Bin) SyncChildrenStates() bool { return gobool(C.gst_bin_sync_children_states(b.Instance())) } +// DEBUG OPERATIONS // + +// DebugGraphDetails casts GstDebugGraphDetails +type DebugGraphDetails int + +// Type castings +const ( + DebugGraphShowMediaType DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE // (1) – show caps-name on edges + DebugGraphShowCapsDetails DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS // (2) – show caps-details on edges + DebugGraphShowNonDefaultParams DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS // (4) – show modified parameters on elements + DebugGraphShowStates DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_STATES // (8) – show element states + DebugGraphShowPullParams DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_FULL_PARAMS // (16) – show full element parameter values even if they are very long + DebugGraphShowAll DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_ALL // (15) – show all the typical details that one might want + DebugGraphShowVerbose DebugGraphDetails = C.GST_DEBUG_GRAPH_SHOW_VERBOSE // (4294967295) – show all details regardless of how large or verbose they make the resulting output +) + +// DebugBinToDotData will obtain the whole network of gstreamer elements that form the pipeline into a dot file. +// This data can be processed with graphviz to get an image. +func (b *Bin) DebugBinToDotData(details DebugGraphDetails) string { + ret := C.gst_debug_bin_to_dot_data(b.Instance(), C.GstDebugGraphDetails(details)) + defer C.g_free((C.gpointer)(unsafe.Pointer(ret))) + return C.GoString(ret) +} + +// DebugBinToDotFile is like DebugBinToDotData except it will write the dot data to the filename +// specified. +func (b *Bin) DebugBinToDotFile(details DebugGraphDetails, filename string) { + cname := C.CString(filename) + defer C.free(unsafe.Pointer(cname)) + C.gst_debug_bin_to_dot_file(b.Instance(), C.GstDebugGraphDetails(details), (*C.gchar)(unsafe.Pointer(cname))) +} + +// DebugBinToDotFileWithTs is like DebugBinToDotFile except it will write the dot data to the filename +// specified, except it will append the current timestamp to the filename. +func (b *Bin) DebugBinToDotFileWithTs(details DebugGraphDetails, filename string) { + cname := C.CString(filename) + defer C.free(unsafe.Pointer(cname)) + C.gst_debug_bin_to_dot_file_with_ts(b.Instance(), C.GstDebugGraphDetails(details), (*C.gchar)(unsafe.Pointer(cname))) +} + func iteratorToElementSlice(iterator *C.GstIterator) ([]*Element, error) { elems := make([]*Element, 0) gval := new(C.GValue) diff --git a/gst/gst_element.go b/gst/gst_element.go index 7fc864f..9722593 100644 --- a/gst/gst_element.go +++ b/gst/gst_element.go @@ -255,13 +255,31 @@ func (e *Element) SendEvent(ev *Event) bool { // 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. - glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) + if e.TypeFromInstance() != glib.Type(C.GST_TYPE_ELEMENT) { + glib.RegisterGValueMarshalers([]glib.TypeMarshaler{{T: e.TypeFromInstance(), F: marshalElement}}) + } 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...) +} + // SyncStateWithParent tries to change the state of the element to the same as its parent. If this function returns // FALSE, the state of element is undefined. func (e *Element) SyncStateWithParent() bool { diff --git a/gst/gst_stream.go b/gst/gst_stream.go index 293d1a3..e4c5bf9 100644 --- a/gst/gst_stream.go +++ b/gst/gst_stream.go @@ -47,7 +47,11 @@ func (s *Stream) StreamType() StreamType { // Tags returns the tag list for this stream. func (s *Stream) Tags() *TagList { - return wrapTagList(C.gst_stream_get_tags(s.Instance())) + tags := C.gst_stream_get_tags(s.Instance()) + if tags == nil { + return nil + } + return wrapTagList(tags) } // SetCaps sets the caps for this stream.