initial commit

This commit is contained in:
tinyzimmer
2020-09-24 21:34:29 +03:00
parent b8cf8bc28d
commit ced3c5e6f8
35 changed files with 4484 additions and 1 deletions

18
.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Build outputs
dist/
.vscode/

7
Makefile Normal file
View File

@@ -0,0 +1,7 @@
build-cmd:
cd cmd/go-gst && go build -o ../../dist/go-gst
ARGS ?=
run-cmd: build-cmd
dist/go-gst $(ARGS)

View File

@@ -1,2 +1,71 @@
# go-gst
Gstreamer bindings and utilities for golang
Go bindings for the gstreamer C library
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-rounded)](https://pkg.go.dev/github.com/tinyzimmer/go-gst/gst)
[![godoc reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-rounded)](https://godoc.org/github.com/tinyzimmer/go-gst/gst)
This package was originally written to aid the audio support in [`kvdi`](https://github.com/tinyzimmer/kvdi).
But it made sense to turn it into an independent, consumable package. The intention now is to progressively implement the entire API.
See the go.dev reference for documentation and examples.
For other examples see the command line implementation [here](cmd/go-gst).
_TODO: Write examples on programatically building the pipeline yourself_
## Requirements
For building applications with this library you need the following:
- `cgo`: You must set `CGO_ENABLED=1` in your environment when building
- `pkg-config`
- `libgstreamer-1.0-dev`: This package name may be different depending on your OS. You need the `gst.h` header files.
For running applications with this library you'll need to have `libgstreamer-1.0` installed. Again, this package may be different depending on your OS.
## CLI
There is a CLI utility included with this package that demonstrates some of the things you can do.
For now the functionality is limitted to GIF encoing and other arbitrary pipelines.
If I extend it further I'll publish releases, but for now, you can retrieve it with `go get`.
```bash
go get github.com/tinyzimmer/go-gst-launch/cmd/go-gst-launch
```
The usage is described below:
```
Go-gst is a CLI utility aiming to implement the core functionality
of the core gstreamer-tools. It's primary purpose is to showcase the functionality of
the underlying go-gst library.
There are also additional commands showing some of the things you can do with the library,
such as websocket servers reading/writing to/from local audio servers and audio/video/image
encoders/decoders.
Usage:
go-gst [command]
Available Commands:
completion Generate completion script
gif Encodes the given video to GIF format
help Help about any command
inspect Inspect the elements of the given pipeline string
launch Run a generic pipeline
websocket Run a websocket audio proxy for streaming audio from a pulse server
and optionally recording to a virtual mic.
Flags:
-I, --from-stdin Write to the pipeline from stdin. If this is specified, then -i is ignored.
-h, --help help for go-gst
-i, --input string An input file, defaults to the first element in the pipeline.
-o, --output string An output file, defaults to the last element in the pipeline.
-O, --to-stdout Writes the results from the pipeline to stdout. If this is specified, then -o is ignored.
-v, --verbose Verbose output. This is ignored when used with --to-stdout.
Use "go-gst [command] --help" for more information about a command.
```

66
cmd/go-gst/colors.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
"io"
"strings"
)
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) 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...)))
}

62
cmd/go-gst/completion.go Normal file
View File

@@ -0,0 +1,62 @@
package main
import (
"os"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(completionCmd)
}
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `To load completions:
Bash:
$ source <(yourprogram completion bash)
# To load completions for each session, execute once:
Linux:
$ yourprogram completion bash > /etc/bash_completion.d/yourprogram
MacOS:
$ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ yourprogram completion zsh > "${fpath[1]}/_yourprogram"
# You will need to start a new shell for this setup to take effect.
Fish:
$ yourprogram completion fish | source
# To load completions for each session, execute once:
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletion(os.Stdout)
}
},
}

144
cmd/go-gst/gif.go Normal file
View File

@@ -0,0 +1,144 @@
package main
import (
"errors"
"fmt"
"image"
"image/color/palette"
"image/gif"
"image/jpeg"
"image/png"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinyzimmer/go-gst-launch/gst"
"github.com/spf13/cobra"
)
var framesPerSecond int
var imageFormat string
func init() {
gifCmd.PersistentFlags().IntVarP(&framesPerSecond, "frame-rate", "r", 10, "The number of frames per-second to encode into the GIF")
gifCmd.PersistentFlags().StringVarP(&imageFormat, "format", "f", "png", "The image format to encode frames to")
rootCmd.AddCommand(gifCmd)
}
var gifCmd = &cobra.Command{
Use: "gif",
Short: "Encodes the given video to GIF format",
Long: `Look at the available options to change the compression levels and format.
Requires libav be installed.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if srcFile == "" && !fromStdin {
return errors.New("No input provided")
}
if destFile == "" && !toStdout {
return errors.New("No output provided")
}
return nil
},
RunE: gifEncode,
}
func gifEncode(cmd *cobra.Command, args []string) error {
var imageEncoder string
var decoder func(io.Reader) (image.Image, error)
switch strings.ToLower(imageFormat) {
case "png":
imageEncoder = "pngenc"
decoder = png.Decode
case "jpg":
imageEncoder = "jpegenc"
decoder = jpeg.Decode
case "jpeg":
imageEncoder = "jpegenc"
decoder = jpeg.Decode
default:
return fmt.Errorf("Invalid image format %s: Valid options [ png | jpg ]", strings.ToLower(imageFormat))
}
tmpDir, err := ioutil.TempDir("", "")
defer os.RemoveAll(tmpDir)
launchStr := fmt.Sprintf(
`filesrc location=%s ! decodebin ! videoconvert ! videoscale ! videorate ! video/x-raw,framerate=%d/1 ! %s ! multifilesink location="%s/%%04d.%s"`,
srcFile, framesPerSecond, imageEncoder, tmpDir, strings.ToLower(imageFormat),
)
logInfo("gif", "Converting video to image frames")
gstPipeline, err := gst.NewPipelineFromLaunchString(launchStr, gst.PipelineInternalOnly)
if err != nil {
return err
}
defer gstPipeline.Close()
if err := gstPipeline.Start(); err != nil {
return err
}
if verbose {
setupVerbosePipelineListeners(gstPipeline, "gif")
}
gst.Wait(gstPipeline)
logInfo("gif", "Building output gif:", destFile)
dest, err := getDestFile()
if err != nil {
return err
}
files, err := ioutil.ReadDir(tmpDir)
if err != nil {
return err
}
outGif := &gif.GIF{
Image: make([]*image.Paletted, 0),
Delay: make([]int, 0),
}
numFrames := len(files)
for idx, file := range files {
if !toStdout {
fmt.Printf("\rEncoding frame %d/%d", idx, numFrames)
}
f, err := os.Open(filepath.Join(tmpDir, file.Name()))
if err != nil {
return err
}
img, err := decoder(f)
if err != nil {
return err
}
frame := image.NewPaletted(img.Bounds(), palette.Plan9)
for x := 1; x <= img.Bounds().Dx(); x++ {
for y := 1; y <= img.Bounds().Dy(); y++ {
frame.Set(x, y, img.At(x, y))
}
}
outGif.Image = append(outGif.Image, frame)
outGif.Delay = append(outGif.Delay, 0)
}
if err := gif.EncodeAll(dest, outGif); err != nil {
return err
}
if !toStdout {
fmt.Println()
}
logInfo("gif", "If the command reached this state and you see a GStreamer-CRITICAL error, you can ignore it")
return nil
}

325
cmd/go-gst/inspect.go Normal file
View File

@@ -0,0 +1,325 @@
package main
import (
"bytes"
"errors"
"fmt"
"strings"
"text/tabwriter"
"github.com/gotk3/gotk3/glib"
"github.com/spf13/cobra"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func init() {
rootCmd.AddCommand(inspectCmd)
}
var inspectCmd = &cobra.Command{
Use: "inspect",
Short: "Inspect the elements of the given pipeline string",
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("You must specify an object to inspect")
}
return nil
},
RunE: inspect,
}
func inspect(cmd *cobra.Command, args []string) error {
name := args[0]
// load the registry
registry := gst.GetRegistry()
// get the factory for the element
factory := gst.Find(name)
if factory == nil {
return errors.New("Could not get details for factory")
}
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(": ")
caps := tmpl.Caps()
if len(caps) == 0 {
colorOrange.printIndent(6, "ANY")
} else {
printCaps(&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, "selement 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()
}
fmt.Println()
colorOrange.print("URI handling capabilities:\n")
colorLightGray.printfIndent(2, "Element can act as %s.\n", strings.ToLower(elem.GetURIType().String()))
protos := elem.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) {}
func printCaps(caps *gst.Caps, indent int) {
for _, cap := range *caps {
colorReset.print("\n")
colorOrange.printfIndent(indent, "%s", cap.Name)
for k, v := range cap.Data {
colorReset.print("\n")
colorOrange.printfIndent(indent+2, "%s", k)
colorReset.print(": ")
colorLightGray.print(fmt.Sprint(v))
}
}
fmt.Println()
}

73
cmd/go-gst/launch.go Normal file
View File

@@ -0,0 +1,73 @@
package main
import (
"errors"
"io"
"strings"
"github.com/spf13/cobra"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func init() {
rootCmd.AddCommand(launchCmd)
}
var launchCmd = &cobra.Command{
Use: "launch",
Short: "Run a generic pipeline",
Long: `Uses the provided pipeline string to encode/decode the data in the pipeline.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("The pipeline string cannot be empty")
}
return nil
},
RunE: launch,
}
func launch(cmd *cobra.Command, args []string) error {
src, dest, err := getCLIFiles()
if err != nil {
return err
}
var flags gst.PipelineFlags
if src != nil {
flags = flags | gst.PipelineWrite
}
if dest != nil {
flags = flags | gst.PipelineRead
}
pipelineString := strings.Join(args, " ")
logInfo("pipeline", "Creating pipeline")
gstPipeline, err := gst.NewPipelineFromLaunchString(pipelineString, flags)
if err != nil {
return err
}
defer gstPipeline.Close()
if verbose {
setupVerbosePipelineListeners(gstPipeline, "pipeline")
}
logInfo("pipeline", "Starting pipeline")
if err := gstPipeline.Start(); err != nil {
return err
}
if src != nil {
go io.Copy(gstPipeline, src)
}
if dest != nil {
go io.Copy(dest, gstPipeline)
}
gst.Wait(gstPipeline)
return nil
}

14
cmd/go-gst/log.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"log"
)
func logInfo(name string, args ...interface{}) {
nArgs := []interface{}{fmt.Sprintf("[%s]", name)}
nArgs = append(nArgs, args...)
if !toStdout {
log.Println(nArgs...)
}
}

47
cmd/go-gst/main.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/tinyzimmer/go-gst-launch/gst"
)
var (
srcFile, destFile, pipelineStr string
verbose, fromStdin, toStdout bool
rootCmd = &cobra.Command{
Use: "go-gst",
Short: "A command-line audio/video encoder and decoder based on gstreamer",
Long: `Go-gst is a CLI utility aiming to implement the core functionality
of the core gstreamer-tools. It's primary purpose is to showcase the functionality of
the underlying go-gst library.
There are also additional commands showing some of the things you can do with the library,
such as websocket servers reading/writing to/from local audio servers and audio/video/image
encoders/decoders.
`,
}
)
func init() {
gst.Init()
rootCmd.PersistentFlags().StringVarP(&srcFile, "input", "i", "", "An input file, defaults to the first element in the pipeline.")
rootCmd.PersistentFlags().StringVarP(&destFile, "output", "o", "", "An output file, defaults to the last element in the pipeline.")
rootCmd.PersistentFlags().BoolVarP(&fromStdin, "from-stdin", "I", false, "Write to the pipeline from stdin. If this is specified, then -i is ignored.")
rootCmd.PersistentFlags().BoolVarP(&toStdout, "to-stdout", "O", false, "Writes the results from the pipeline to stdout. If this is specified, then -o is ignored.")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output. This is ignored when used with --to-stdout.")
}
// Execute executes the root command.
func Execute() error {
return rootCmd.Execute()
}
func main() {
if err := Execute(); err != nil {
log.Println("ERROR:", err.Error())
}
}

View File

@@ -0,0 +1,98 @@
package main
import (
"fmt"
"github.com/gotk3/gotk3/glib"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func printFieldType(s string) {
colorGreen.printIndent(24, s)
}
func printFieldName(s string) {
colorOrange.print(s)
colorReset.print(": ")
}
func printFieldValue(s string) {
colorCyan.print(s)
}
func printObjectPropertiesInfo(obj *gst.Object, description string) {
colorOrange.printf("%s:\n", description)
// for now this function only handles elements
for _, param := range obj.ListProperties() {
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())
colorReset.print("\n")
switch param.ValueType {
case glib.TYPE_STRING:
printFieldType("String. ")
printFieldName("Default")
if param.DefaultValue == nil {
printFieldValue("null")
} else {
str, _ := param.DefaultValue.GetString()
printFieldValue(str)
}
case glib.TYPE_BOOLEAN:
val, err := param.DefaultValue.GoValue()
var valStr string
if err != nil {
valStr = "unknown" // edge case
} else {
b := val.(bool)
valStr = fmt.Sprintf("%t", b)
}
printFieldType("Boolean. ")
printFieldName("Default")
printFieldValue(valStr)
case glib.TYPE_ULONG:
printFieldType("Unsigned Long. ")
case glib.TYPE_LONG:
printFieldType("Long. ")
case glib.TYPE_UINT:
printFieldType("Unsigned Integer. ")
case glib.TYPE_INT:
printFieldType("Integer. ")
case glib.TYPE_UINT64:
printFieldType("Unsigned Integer64. ")
case glib.TYPE_INT64:
printFieldType("Integer64. ")
case glib.TYPE_FLOAT:
printFieldType("Float. ")
case glib.TYPE_DOUBLE:
printFieldType("Double. ")
default:
}
colorReset.print("\n")
}
fmt.Println()
}

98
cmd/go-gst/util.go Normal file
View File

@@ -0,0 +1,98 @@
package main
import (
"os"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func getSrcFile() (*os.File, error) {
if fromStdin || srcFile != "" {
if fromStdin {
if verbose {
logInfo("file", "Reading media data from stdin")
}
return os.Stdin, nil
}
if verbose {
logInfo("file", "Reading media data from", srcFile)
}
return os.Open(srcFile)
}
// Commands should do internal checking before calling this command
return nil, nil
}
func getDestFile() (*os.File, error) {
if toStdout || destFile != "" {
if toStdout {
if verbose {
logInfo("file", "Writing media output to stdout")
}
return os.Stdout, nil
}
if verbose {
logInfo("file", "Writing media output to", destFile)
}
return os.Create(destFile)
}
// Commands should do internal checking before calling this command
return nil, nil
}
func getCLIFiles() (src, dest *os.File, err error) {
src, err = getSrcFile()
if err != nil {
return nil, nil, err
}
dest, err = getDestFile()
if err != nil {
src.Close()
return nil, nil, err
}
// Commands should do internal checking before calling this command
return src, dest, nil
}
func setupVerbosePipelineListeners(gstPipeline *gst.Pipeline, name string) {
logInfo(name, "Starting message listeners")
go func() {
var currentState gst.State
for msg := range gstPipeline.GetBus().MessageChan() {
defer msg.Unref()
switch msg.Type() {
case gst.MessageStreamStart:
logInfo(name, "Stream has started")
case gst.MessageEOS:
logInfo(name, "Stream has ended")
case gst.MessageStateChanged:
if currentState != gstPipeline.GetState() {
logInfo(name, "New pipeline state:", gstPipeline.GetState().String())
currentState = gstPipeline.GetState()
}
case gst.MessageInfo:
info := msg.ParseInfo()
logInfo(name, info.Message())
for k, v := range info.Details() {
logInfo(name, k, ":", v)
}
case gst.MessageWarning:
info := msg.ParseWarning()
logInfo(name, "WARNING:", info.Message())
for k, v := range info.Details() {
logInfo(name, k, ":", v)
}
case gst.MessageError:
err := msg.ParseError()
logInfo(name, "ERROR:", err.Error())
logInfo(name, "DEBUG:", err.DebugString())
}
}
}()
}

308
cmd/go-gst/websocket.go Normal file
View File

@@ -0,0 +1,308 @@
package main
import (
"errors"
"fmt"
"io"
"net/http"
"os/exec"
"os/user"
"strings"
"github.com/spf13/cobra"
"github.com/tinyzimmer/go-gst-launch/gst"
"golang.org/x/net/websocket"
)
var (
websocketHost string
websocketPort int
pulseServer, pulseMonitor, encoding, micName, micFifo, micFormat string
micSampleRate, micChannels int
)
func init() {
user, err := user.Current()
var defaultPulseServer, defaultPulseMonitor string
if err == nil {
defaultPulseServer = fmt.Sprintf("/run/user/%s/pulse/native", user.Uid)
}
defaultMonitor, err := exec.Command("/bin/sh", "-c", "pactl list sources | grep Name | head -n1 | cut -d ' ' -f2").Output()
if err == nil {
defaultPulseMonitor = strings.TrimSpace(string(defaultMonitor))
}
websocketCmd.PersistentFlags().StringVarP(&websocketHost, "host", "H", "127.0.0.1", "The host to listen on for websocket connections.")
websocketCmd.PersistentFlags().IntVarP(&websocketPort, "port", "P", 8080, "The port to listen on for websocket connections.")
websocketCmd.PersistentFlags().StringVarP(&pulseServer, "pulse-server", "p", defaultPulseServer, "The path to the PulseAudio socket.")
websocketCmd.PersistentFlags().StringVarP(&pulseMonitor, "pulse-monitor", "d", defaultPulseMonitor, "The monitor device to connect to on the Pulse server. The default device is selected if omitted.")
websocketCmd.PersistentFlags().StringVarP(&encoding, "encoding", "e", "", `The audio encoding to send to websocket connections. The options are:
opus (default)
Serves audio data in webm/opus.
The MediaSource can consume this format by specifying "audio/webm".
vorbis
Serves audio data in ogg/vorbis.
`)
websocketCmd.PersistentFlags().StringVarP(&micFifo, "mic-path", "m", "", "A mic FIFO to write received audio data to, by default, nothing is done with received data.")
websocketCmd.PersistentFlags().StringVarP(&micName, "mic-name", "n", "virtmic", "The name of the mic fifo device in pulse audio.")
websocketCmd.PersistentFlags().StringVarP(&micFormat, "mic-format", "f", "S16LE", "The audio format pulse audio expects on the fifo.")
websocketCmd.PersistentFlags().IntVarP(&micSampleRate, "mic-sample-rate", "r", 16000, "The sample rate pulse audio expects on the fifo.")
websocketCmd.PersistentFlags().IntVarP(&micChannels, "mic-channels", "c", 1, "The number of channels pulse audio expects on the fifo.")
rootCmd.AddCommand(websocketCmd)
}
var websocketCmd = &cobra.Command{
Use: "websocket",
Short: `Run a websocket audio proxy for streaming audio from a pulse server
and optionally recording to a virtual mic.`,
Long: `Starts a websocket server with the given configurations.
This currently only works with PulseAudio or an input file, but may be expanded to be cross-platform.
This command may be expanded to include video support via RFB or RTP.
To use with the mic support, you should first set up a virtual device with something like:
pactl load-module module-pipe-source source_name=virtmic file=/tmp/mic.fifo format=s16le rate=16000 channels=1
And then you can run this command with --mic-path /tmp/mic.fifo. The received data will be expected to
be in the same format as specified with --encoding.
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if pulseServer == "" {
return errors.New("Could not determine pulse server, you should use --pulse-server")
}
switch encoding {
case "opus":
case "vorbis":
case "":
encoding = "opus"
default:
return fmt.Errorf("Not a valid audio encoder: %s", encoding)
}
return nil
},
RunE: websocketProxy,
}
func websocketProxy(cmd *cobra.Command, args []string) error {
addr := fmt.Sprintf("%s:%d", websocketHost, websocketPort)
server := &http.Server{
Handler: &websocket.Server{
Handshake: func(*websocket.Config, *http.Request) error { return nil },
Handler: handleWebsocketConnection,
},
Addr: addr,
}
logInfo("websocket", "Listening on", addr)
return server.ListenAndServe()
}
func handleWebsocketConnection(wsconn *websocket.Conn) {
defer func() {
wsconn.Close()
logInfo("websocket", "Connection to", wsconn.Request().RemoteAddr, "closed")
}()
logInfo("websocket", "New connection from", wsconn.Request().RemoteAddr)
wsconn.PayloadType = websocket.BinaryFrame
var playbackPipeline, recordingPipeline, sinkPipeline *gst.Pipeline
var err error
playbackPipeline, err = newPlaybackPipeline()
if err != nil {
logInfo("websocket", "ERROR:", err.Error())
return
}
playbackPipeline.SetAutoFlush(true)
logInfo("websocket", "Starting playback pipeline")
if err = playbackPipeline.Start(); err != nil {
logInfo("websocket", "ERROR:", err.Error())
return
}
if verbose {
setupVerbosePipelineListeners(playbackPipeline, "playback")
}
if micFifo != "" {
recordingPipeline, err = newRecordingPipeline()
if err != nil {
logInfo("websocket", "Could not open pipeline for recording:", err.Error())
return
}
defer recordingPipeline.Close()
sinkPipeline, err = newSinkPipeline()
if err != nil {
logInfo("websocket", "Could not open null sink pipeling. Disabling recording.")
return
}
defer sinkPipeline.Close()
}
if recordingPipeline != nil && sinkPipeline != nil {
logInfo("websocket", "Starting recording pipeline")
if err = recordingPipeline.Start(); err != nil {
logInfo("websocket", "Could not start recording pipeline")
return
}
logInfo("websocket", "Starting sink pipeline")
if err = sinkPipeline.Start(); err != nil {
logInfo("websocket", "Could not start sink pipeline")
return
}
if verbose {
setupVerbosePipelineListeners(sinkPipeline, "mic-null-sink")
}
var runMicFunc func()
runMicFunc = func() {
if verbose {
setupVerbosePipelineListeners(recordingPipeline, "recorder")
}
go io.Copy(recordingPipeline, wsconn)
go func() {
var lastState gst.State
for msg := range recordingPipeline.GetBus().MessageChan() {
defer msg.Unref()
switch msg.Type() {
case gst.MessageStateChanged:
if lastState == gst.StatePlaying && recordingPipeline.GetState() != gst.StatePlaying {
var nerr error
recordingPipeline.Close()
recordingPipeline, nerr = newRecordingPipeline()
if nerr != nil {
logInfo("websocket", "Could not create new recording pipeline, stopping input stream")
return
}
logInfo("websocket", "Restarting recording pipeline")
if nerr = recordingPipeline.Start(); nerr != nil {
logInfo("websocket", "Could not start new recording pipeline, stopping input stream")
}
runMicFunc()
return
}
lastState = recordingPipeline.GetState()
}
}
}()
}
runMicFunc()
}
go func() {
io.Copy(wsconn, playbackPipeline)
playbackPipeline.Close()
}()
if srcFile != "" {
srcFile, err := getSrcFile()
if err != nil {
return
}
defer srcFile.Close()
stat, err := srcFile.Stat()
if err != nil {
return
}
appSrc := playbackPipeline.GetAppSrc()
appSrc.SetSize(stat.Size())
appSrc.PushBuffer(srcFile)
for {
if ret := appSrc.EndStream(); ret == gst.FlowOK {
break
}
}
}
gst.Wait(playbackPipeline)
}
func newPlaybackPipelineFromString() (*gst.Pipeline, error) {
pipelineString := "decodebin ! audioconvert ! audioresample"
switch encoding {
case "opus":
pipelineString = fmt.Sprintf("%s ! cutter ! opusenc ! webmmux", pipelineString)
case "vorbis":
pipelineString = fmt.Sprintf("%s ! vorbisenc ! oggmux", pipelineString)
}
if verbose {
logInfo("playback", "Using pipeline string", pipelineString)
}
return gst.NewPipelineFromLaunchString(pipelineString, gst.PipelineReadWrite|gst.PipelineUseGstApp)
}
func newPlaybackPipeline() (*gst.Pipeline, error) {
if srcFile != "" {
return newPlaybackPipelineFromString()
}
cfg := &gst.PipelineConfig{Elements: []*gst.PipelineElement{}}
pulseSrc := &gst.PipelineElement{
Name: "pulsesrc",
Data: map[string]interface{}{"server": pulseServer},
SinkCaps: gst.NewRawCaps("S16LE", 24000, 2),
}
if pulseMonitor != "" {
pulseSrc.Data["device"] = pulseMonitor
}
cfg.Elements = append(cfg.Elements, pulseSrc)
switch encoding {
case "opus":
cfg.Elements = append(cfg.Elements, &gst.PipelineElement{Name: "cutter"})
cfg.Elements = append(cfg.Elements, &gst.PipelineElement{Name: "opusenc"})
cfg.Elements = append(cfg.Elements, &gst.PipelineElement{Name: "webmmux"})
case "vorbis":
cfg.Elements = append(cfg.Elements, &gst.PipelineElement{Name: "vorbisenc"})
cfg.Elements = append(cfg.Elements, &gst.PipelineElement{Name: "oggmux"})
}
return gst.NewPipelineFromConfig(cfg, gst.PipelineRead|gst.PipelineUseGstApp, nil)
}
func newRecordingPipeline() (*gst.Pipeline, error) {
return gst.NewPipelineFromLaunchString(newPipelineStringFromOpts(), gst.PipelineWrite)
}
func newPipelineStringFromOpts() string {
return fmt.Sprintf(
"decodebin ! audioconvert ! audioresample ! audio/x-raw, format=%s, rate=%d, channels=%d ! filesink location=%s append=true",
micFormat,
micSampleRate,
micChannels,
micFifo,
)
}
func newSinkPipeline() (*gst.Pipeline, error) {
cfg := &gst.PipelineConfig{
Elements: []*gst.PipelineElement{
{
Name: "pulsesrc",
Data: map[string]interface{}{"server": pulseServer, "device": micName},
SinkCaps: gst.NewRawCaps(micFormat, micSampleRate, micChannels),
},
{
Name: "fakesink",
Data: map[string]interface{}{"sync": false},
},
},
}
return gst.NewPipelineFromConfig(cfg, gst.PipelineInternalOnly, nil)
}

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module github.com/tinyzimmer/go-gst-launch
go 1.15
require (
github.com/gotk3/gotk3 v0.4.0
github.com/spf13/cobra v1.0.0
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
)

130
go.sum Normal file
View File

@@ -0,0 +1,130 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gotk3/gotk3 v0.4.0 h1:TIuhyQitGeRTxOQIV3AJlYtEWWJpC74JHwAIsxlH8MU=
github.com/gotk3/gotk3 v0.4.0/go.mod h1:Eew3QBwAOBTrfFFDmsDE5wZWbcagBL1NUslj1GhRveo=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

97
gst/c_util.go Normal file
View File

@@ -0,0 +1,97 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
*/
import "C"
import (
"errors"
"unsafe"
)
// Init runs `gst_init`. It currently does not support arguments. This should
// be called before building any pipelines.
func Init() {
C.gst_init(nil, nil)
}
// gobool provides an easy type conversion between C.gboolean and a go bool.
func gobool(b C.gboolean) bool {
return b != 0
}
// 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)
}
// structureToGoMap converts a GstStructure into a Go map of strings.
func structureToGoMap(st *C.GstStructure) map[string]string {
goDetails := make(map[string]string)
numFields := int(C.gst_structure_n_fields((*C.GstStructure)(st)))
for i := 0; i < numFields-1; i++ {
fieldName := C.gst_structure_nth_field_name((*C.GstStructure)(st), (C.guint)(i))
fieldValue := C.gst_structure_get_value((*C.GstStructure)(st), (*C.gchar)(fieldName))
strValueDup := C.g_strdup_value_contents((*C.GValue)(fieldValue))
goDetails[C.GoString(fieldName)] = C.GoString(strValueDup)
}
return goDetails
}
// MessageType is an alias to the C equivalent of GstMessageType.
type MessageType C.GstMessageType
// Type casting of GstMessageTypes
const (
MessageAny MessageType = C.GST_MESSAGE_ANY
MessageStreamStart = C.GST_MESSAGE_STREAM_START
MessageEOS = C.GST_MESSAGE_EOS
MessageInfo = C.GST_MESSAGE_INFO
MessageWarning = C.GST_MESSAGE_WARNING
MessageError = C.GST_MESSAGE_ERROR
MessageStateChanged = C.GST_MESSAGE_STATE_CHANGED
MessageElement = C.GST_MESSAGE_ELEMENT
MessageStreamStatus = C.GST_MESSAGE_STREAM_STATUS
MessageBuffering = C.GST_MESSAGE_BUFFERING
MessageLatency = C.GST_MESSAGE_LATENCY
MessageNewClock = C.GST_MESSAGE_NEW_CLOCK
MessageAsyncDone = C.GST_MESSAGE_ASYNC_DONE
MessageTag = C.GST_MESSAGE_TAG
)
func iteratorToElementSlice(iterator *C.GstIterator) ([]*Element, error) {
elems := make([]*Element, 0)
gval := new(C.GValue)
for {
switch C.gst_iterator_next((*C.GstIterator)(iterator), (*C.GValue)(unsafe.Pointer(gval))) {
case C.GST_ITERATOR_DONE:
C.gst_iterator_free((*C.GstIterator)(iterator))
return elems, nil
case C.GST_ITERATOR_RESYNC:
C.gst_iterator_resync((*C.GstIterator)(iterator))
case C.GST_ITERATOR_OK:
cElemVoid := C.g_value_get_object((*C.GValue)(gval))
cElem := (*C.GstElement)(cElemVoid)
elems = append(elems, wrapElement(cElem))
C.g_value_reset((*C.GValue)(gval))
default:
return nil, errors.New("Element iterator failed")
}
}
}
func goStrings(argc C.int, argv **C.gchar) []string {
length := int(argc)
tmpslice := (*[1 << 30]*C.gchar)(unsafe.Pointer(argv))[:length:length]
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
return gostrings
}

141
gst/doc.go Normal file
View File

@@ -0,0 +1,141 @@
/*
Package gst provides wrappers for building gstreamer pipelines and then
reading and/or writing from either end of the pipeline.
It uses cgo to interface with the gstreamer-1.0 C API.
A simple opus/webm encoder created from a launch string could look like this:
import (
"os"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func main() {
gst.Init()
encoder, err := gst.NewPipelineFromLaunchString("opusenc ! webmmux", gst.PipelineReadWrite)
if err != nil {
panic(err)
}
// You should close even if you don't start the pipeline, since this
// will free resources created by gstreamer.
defer encoder.Close()
if err := encoder.Start() ; err != nil {
panic(err)
}
go func() {
encoder.Write(...) // Write raw audio data to the pipeline
}()
// don't actually do this - copy encoded audio to stdout
if _, err := io.Copy(os.Stdout, encoder) ; err != nil {
panic(err)
}
}
You can accomplish the same thing using the "configuration" functionality provided by NewPipelineFromConfig().
Here is an example that will record from a pulse server and make opus/webm data available on the Reader.
import (
"io"
"os"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func main() {
gst.Init()
encoder, err := gst.NewPipelineFromConfig(&gst.PipelineConfig{
Plugins: []*gst.Plugin{
{
Name: "pulsesrc",
Data: map[string]interface{}{
"server": "/run/user/1000/pulse/native",
"device": "playback-device.monitor",
},
SinkCaps: gst.NewRawCaps("S16LE", 24000, 2),
},
{
Name: "opusenc",
},
{
Name: "webmmux",
},
},
}, gst.PipelineRead, nil)
if err != nil {
panic(err)
}
defer encoder.Close()
if err := encoder.Start() ; err != nil {
panic(err)
}
// Create an output file
f, err := os.Create("out.opus")
if err != nil {
panic(err)
}
// Copy the data from the pipeline to the file
if err := io.Copy(f, encoder) ; err != nil {
panic(err)
}
}
There are two channels exported for listening for messages from the pipeline.
An example of listening to messages on a fake pipeline for 10 seconds:
package main
import (
"fmt"
"time"
"github.com/tinyzimmer/go-gst-launch/gst"
)
func main() {
gst.Init()
pipeline, err := gst.NewPipelineFromLaunchString("audiotestsrc ! fakesink", gst.PipelineInternalOnly)
if err != nil {
panic(err)
}
defer pipeline.Close()
go func() {
for msg := range pipeline.MessageChan() {
fmt.Println("Got message:", msg.TypeName())
}
}()
go func() {
for msg := range pipeline.ErrorChan() {
fmt.Println("Got error:", err)
}
}()
if err := pipeline.Start(); err != nil {
fmt.Println("Pipeline failed to start")
return
}
time.Sleep(time.Second * 10)
}
The package also exposes some low level functionality for building pipelines
and doing dynamic linking yourself. See the NewPipeline() function for creating an
empty pipeline that you can then build out using the other structs and methods provided
by this package.
*/
package gst

506
gst/gst.go.h Normal file
View File

@@ -0,0 +1,506 @@
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <gst/app/gstappsrc.h>
/*
Utilitits
*/
static GObjectClass *
getGObjectClass(void * p) {
return G_OBJECT_GET_CLASS (p);
}
static int
sizeOfGCharArray(gchar ** arr) {
int i;
for (i = 0 ; 1 ; i = i + 1) {
if (arr[i] == NULL) { return i; };
}
}
static gboolean
gstObjectFlagIsSet(GstObject * obj, GstElementFlags flags)
{
return GST_OBJECT_FLAG_IS_SET (obj, flags);
}
static gboolean
gstElementIsURIHandler(GstElement * elem)
{
return GST_IS_URI_HANDLER (elem);
}
/*
Type Castings
*/
static GstUri *
toGstURI(void *p)
{
return (GST_URI(p));
}
static GstURIHandler *
toGstURIHandler(void *p)
{
return (GST_URI_HANDLER(p));
}
static GstRegistry *
toGstRegistry(void *p)
{
return (GST_REGISTRY(p));
}
static GstPlugin *
toGstPlugin(void *p)
{
return (GST_PLUGIN(p));
}
static GstPluginFeature *
toGstPluginFeature(void *p)
{
return (GST_PLUGIN_FEATURE(p));
}
static GstObject *
toGstObject(void *p)
{
return (GST_OBJECT(p));
}
static GstElementFactory *
toGstElementFactory(void *p)
{
return (GST_ELEMENT_FACTORY(p));
}
static GstElement *
toGstElement(void *p)
{
return (GST_ELEMENT(p));
}
static GstAppSink *
toGstAppSink(void *p)
{
return (GST_APP_SINK(p));
}
static GstAppSrc *
toGstAppSrc(void *p)
{
return (GST_APP_SRC(p));
}
static GstBin *
toGstBin(void *p)
{
return (GST_BIN(p));
}
static GstBus *
toGstBus(void *p)
{
return (GST_BUS(p));
}
static GstMessage *
toGstMessage(void *p)
{
return (GST_MESSAGE(p));
}
static GstPipeline *
toGstPipeline(void *p)
{
return (GST_PIPELINE(p));
}
static GstPad *
toGstPad(void *p)
{
return (GST_PAD(p));
}
static GstPadTemplate *
toGstPadTemplate(void *p)
{
return (GST_PAD_TEMPLATE(p));
}
static GstStructure *
toGstStructure(void *p)
{
return (GST_STRUCTURE(p));
}
static GstClock *
toGstClock(void *p)
{
return (GST_CLOCK(p));
}
// /* obj will be NULL if we're printing properties of pad template pads */
// static void
// print_object_properties_info (GObject * obj, GObjectClass * obj_class,
// const gchar * desc)
// {
// GParamSpec **property_specs;
// guint num_properties, i;
// gboolean readable;
// gboolean first_flag;
// property_specs = g_object_class_list_properties (obj_class, &num_properties);
// g_qsort_with_data (property_specs, num_properties, sizeof (gpointer),
// (GCompareDataFunc) sort_gparamspecs, NULL);
// n_print ("%s%s%s:\n", HEADING_COLOR, desc, RESET_COLOR);
// push_indent ();
// for (i = 0; i < num_properties; i++) {
// GValue value = { 0, };
// GParamSpec *param = property_specs[i];
// GType owner_type = param->owner_type;
// /* We're printing pad properties */
// if (obj == NULL && (owner_type == G_TYPE_OBJECT
// || owner_type == GST_TYPE_OBJECT || owner_type == GST_TYPE_PAD))
// continue;
// g_value_init (&value, param->value_type);
// n_print ("%s%-20s%s: %s%s%s\n", PROP_NAME_COLOR,
// g_param_spec_get_name (param), RESET_COLOR, PROP_VALUE_COLOR,
// g_param_spec_get_blurb (param), RESET_COLOR);
// push_indent_n (11);
// first_flag = TRUE;
// n_print ("%sflags%s: ", PROP_ATTR_NAME_COLOR, RESET_COLOR);
// readable = ! !(param->flags & G_PARAM_READABLE);
// if (readable && obj != NULL) {
// g_object_get_property (obj, param->name, &value);
// } else {
// /* if we can't read the property value, assume it's set to the default
// * (which might not be entirely true for sub-classes, but that's an
// * unlikely corner-case anyway) */
// g_param_value_set_default (param, &value);
// }
// if (readable) {
// g_print ("%s%s%s%s", (first_flag) ? "" : ", ", PROP_ATTR_VALUE_COLOR,
// _("readable"), RESET_COLOR);
// first_flag = FALSE;
// }
// if (param->flags & G_PARAM_WRITABLE) {
// g_print ("%s%s%s%s", (first_flag) ? "" : ", ", PROP_ATTR_VALUE_COLOR,
// _("writable"), RESET_COLOR);
// first_flag = FALSE;
// }
// if (param->flags & G_PARAM_DEPRECATED) {
// g_print ("%s%s%s%s", (first_flag) ? "" : ", ", PROP_ATTR_VALUE_COLOR,
// _("deprecated"), RESET_COLOR);
// first_flag = FALSE;
// }
// if (param->flags & GST_PARAM_CONTROLLABLE) {
// g_print (", %s%s%s", PROP_ATTR_VALUE_COLOR, _("controllable"),
// RESET_COLOR);
// first_flag = FALSE;
// }
// if (param->flags & GST_PARAM_CONDITIONALLY_AVAILABLE) {
// g_print (", %s%s%s", PROP_ATTR_VALUE_COLOR, _("conditionally available"),
// RESET_COLOR);
// first_flag = FALSE;
// }
// if (param->flags & GST_PARAM_MUTABLE_PLAYING) {
// g_print (", %s%s%s", PROP_ATTR_VALUE_COLOR,
// _("changeable in NULL, READY, PAUSED or PLAYING state"), RESET_COLOR);
// } else if (param->flags & GST_PARAM_MUTABLE_PAUSED) {
// g_print (", %s%s%s", PROP_ATTR_VALUE_COLOR,
// _("changeable only in NULL, READY or PAUSED state"), RESET_COLOR);
// } else if (param->flags & GST_PARAM_MUTABLE_READY) {
// g_print (", %s%s%s", PROP_ATTR_VALUE_COLOR,
// _("changeable only in NULL or READY state"), RESET_COLOR);
// }
// if (param->flags & ~KNOWN_PARAM_FLAGS) {
// g_print ("%s0x%s%0x%s", (first_flag) ? "" : ", ", PROP_ATTR_VALUE_COLOR,
// param->flags & ~KNOWN_PARAM_FLAGS, RESET_COLOR);
// }
// g_print ("\n");
// switch (G_VALUE_TYPE (&value)) {
// case G_TYPE_STRING:
// {
// const char *string_val = g_value_get_string (&value);
// n_print ("%sString%s. ", DATATYPE_COLOR, RESET_COLOR);
// if (string_val == NULL)
// g_print ("%sDefault%s: %snull%s", PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, RESET_COLOR);
// else
// g_print ("%sDefault%s: %s\"%s\"%s", PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, string_val, RESET_COLOR);
// break;
// }
// case G_TYPE_BOOLEAN:
// {
// gboolean bool_val = g_value_get_boolean (&value);
// n_print ("%sBoolean%s. %sDefault%s: %s%s%s", DATATYPE_COLOR,
// RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, bool_val ? "true" : "false", RESET_COLOR);
// break;
// }
// case G_TYPE_ULONG:
// {
// GParamSpecULong *pulong = G_PARAM_SPEC_ULONG (param);
// n_print
// ("%sUnsigned Long%s. %sRange%s: %s%lu - %lu%s %sDefault%s: %s%lu%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, pulong->minimum, pulong->maximum,
// RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, g_value_get_ulong (&value), RESET_COLOR);
// GST_ERROR ("%s: property '%s' of type ulong: consider changing to "
// "uint/uint64", G_OBJECT_CLASS_NAME (obj_class),
// g_param_spec_get_name (param));
// break;
// }
// case G_TYPE_LONG:
// {
// GParamSpecLong *plong = G_PARAM_SPEC_LONG (param);
// n_print ("%sLong%s. %sRange%s: %s%ld - %ld%s %sDefault%s: %s%ld%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, plong->minimum, plong->maximum, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// g_value_get_long (&value), RESET_COLOR);
// GST_ERROR ("%s: property '%s' of type long: consider changing to "
// "int/int64", G_OBJECT_CLASS_NAME (obj_class),
// g_param_spec_get_name (param));
// break;
// }
// case G_TYPE_UINT:
// {
// GParamSpecUInt *puint = G_PARAM_SPEC_UINT (param);
// n_print
// ("%sUnsigned Integer%s. %sRange%s: %s%u - %u%s %sDefault%s: %s%u%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, puint->minimum, puint->maximum, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// g_value_get_uint (&value), RESET_COLOR);
// break;
// }
// case G_TYPE_INT:
// {
// GParamSpecInt *pint = G_PARAM_SPEC_INT (param);
// n_print ("%sInteger%s. %sRange%s: %s%d - %d%s %sDefault%s: %s%d%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, pint->minimum, pint->maximum, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// g_value_get_int (&value), RESET_COLOR);
// break;
// }
// case G_TYPE_UINT64:
// {
// GParamSpecUInt64 *puint64 = G_PARAM_SPEC_UINT64 (param);
// n_print ("%sUnsigned Integer64%s. %sRange%s: %s%" G_GUINT64_FORMAT " - "
// "%" G_GUINT64_FORMAT "%s %sDefault%s: %s%" G_GUINT64_FORMAT "%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, puint64->minimum, puint64->maximum,
// RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, g_value_get_uint64 (&value), RESET_COLOR);
// break;
// }
// case G_TYPE_INT64:
// {
// GParamSpecInt64 *pint64 = G_PARAM_SPEC_INT64 (param);
// n_print ("%sInteger64%s. %sRange%s: %s%" G_GINT64_FORMAT " - %"
// G_GINT64_FORMAT "%s %sDefault%s: %s%" G_GINT64_FORMAT "%s ",
// DATATYPE_COLOR, RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, pint64->minimum, pint64->maximum,
// RESET_COLOR, PROP_ATTR_NAME_COLOR, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, g_value_get_int64 (&value), RESET_COLOR);
// break;
// }
// case G_TYPE_FLOAT:
// {
// GParamSpecFloat *pfloat = G_PARAM_SPEC_FLOAT (param);
// n_print ("%sFloat%s. %sRange%s: %s%15.7g - %15.7g%s "
// "%sDefault%s: %s%15.7g%s ", DATATYPE_COLOR, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// pfloat->minimum, pfloat->maximum, RESET_COLOR, PROP_ATTR_NAME_COLOR,
// RESET_COLOR, PROP_ATTR_VALUE_COLOR, g_value_get_float (&value),
// RESET_COLOR);
// break;
// }
// case G_TYPE_DOUBLE:
// {
// GParamSpecDouble *pdouble = G_PARAM_SPEC_DOUBLE (param);
// n_print ("%sDouble%s. %sRange%s: %s%15.7g - %15.7g%s "
// "%sDefault%s: %s%15.7g%s ", DATATYPE_COLOR, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// pdouble->minimum, pdouble->maximum, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// g_value_get_double (&value), RESET_COLOR);
// break;
// }
// case G_TYPE_CHAR:
// case G_TYPE_UCHAR:
// GST_ERROR ("%s: property '%s' of type char: consider changing to "
// "int/string", G_OBJECT_CLASS_NAME (obj_class),
// g_param_spec_get_name (param));
// /* fall through */
// default:
// if (param->value_type == GST_TYPE_CAPS) {
// const GstCaps *caps = gst_value_get_caps (&value);
// if (!caps)
// n_print ("%sCaps%s (NULL)", DATATYPE_COLOR, RESET_COLOR);
// else {
// print_caps (caps, " ");
// }
// } else if (G_IS_PARAM_SPEC_ENUM (param)) {
// GEnumValue *values;
// guint j = 0;
// gint enum_value;
// const gchar *value_nick = "";
// values = G_ENUM_CLASS (g_type_class_ref (param->value_type))->values;
// enum_value = g_value_get_enum (&value);
// while (values[j].value_name) {
// if (values[j].value == enum_value)
// value_nick = values[j].value_nick;
// j++;
// }
// n_print ("%sEnum \"%s\"%s %sDefault%s: %s%d, \"%s\"%s",
// DATATYPE_COLOR, g_type_name (G_VALUE_TYPE (&value)), RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// enum_value, value_nick, RESET_COLOR);
// j = 0;
// while (values[j].value_name) {
// g_print ("\n");
// n_print (" %s(%d)%s: %s%-16s%s - %s%s%s",
// PROP_ATTR_NAME_COLOR, values[j].value, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, values[j].value_nick, RESET_COLOR,
// DESC_COLOR, values[j].value_name, RESET_COLOR);
// j++;
// }
// /* g_type_class_unref (ec); */
// } else if (G_IS_PARAM_SPEC_FLAGS (param)) {
// GParamSpecFlags *pflags = G_PARAM_SPEC_FLAGS (param);
// GFlagsValue *vals;
// gchar *cur;
// vals = pflags->flags_class->values;
// cur = flags_to_string (vals, g_value_get_flags (&value));
// n_print ("%sFlags \"%s\"%s %sDefault%s: %s0x%08x, \"%s\"%s",
// DATATYPE_COLOR, g_type_name (G_VALUE_TYPE (&value)), RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// g_value_get_flags (&value), cur, RESET_COLOR);
// while (vals[0].value_name) {
// g_print ("\n");
// n_print (" %s(0x%08x)%s: %s%-16s%s - %s%s%s",
// PROP_ATTR_NAME_COLOR, vals[0].value, RESET_COLOR,
// PROP_ATTR_VALUE_COLOR, vals[0].value_nick, RESET_COLOR,
// DESC_COLOR, vals[0].value_name, RESET_COLOR);
// ++vals;
// }
// g_free (cur);
// } else if (G_IS_PARAM_SPEC_OBJECT (param)) {
// n_print ("%sObject of type%s %s\"%s\"%s", PROP_VALUE_COLOR,
// RESET_COLOR, DATATYPE_COLOR,
// g_type_name (param->value_type), RESET_COLOR);
// } else if (G_IS_PARAM_SPEC_BOXED (param)) {
// n_print ("%sBoxed pointer of type%s %s\"%s\"%s", PROP_VALUE_COLOR,
// RESET_COLOR, DATATYPE_COLOR,
// g_type_name (param->value_type), RESET_COLOR);
// if (param->value_type == GST_TYPE_STRUCTURE) {
// const GstStructure *s = gst_value_get_structure (&value);
// if (s) {
// g_print ("\n");
// gst_structure_foreach (s, print_field,
// (gpointer) " ");
// }
// }
// } else if (G_IS_PARAM_SPEC_POINTER (param)) {
// if (param->value_type != G_TYPE_POINTER) {
// n_print ("%sPointer of type%s %s\"%s\"%s.", PROP_VALUE_COLOR,
// RESET_COLOR, DATATYPE_COLOR, g_type_name (param->value_type),
// RESET_COLOR);
// } else {
// n_print ("%sPointer.%s", PROP_VALUE_COLOR, RESET_COLOR);
// }
// } else if (param->value_type == G_TYPE_VALUE_ARRAY) {
// GParamSpecValueArray *pvarray = G_PARAM_SPEC_VALUE_ARRAY (param);
// if (pvarray->element_spec) {
// n_print ("%sArray of GValues of type%s %s\"%s\"%s",
// PROP_VALUE_COLOR, RESET_COLOR, DATATYPE_COLOR,
// g_type_name (pvarray->element_spec->value_type), RESET_COLOR);
// } else {
// n_print ("%sArray of GValues%s", PROP_VALUE_COLOR, RESET_COLOR);
// }
// } else if (GST_IS_PARAM_SPEC_FRACTION (param)) {
// GstParamSpecFraction *pfraction = GST_PARAM_SPEC_FRACTION (param);
// n_print ("%sFraction%s. %sRange%s: %s%d/%d - %d/%d%s "
// "%sDefault%s: %s%d/%d%s ", DATATYPE_COLOR, RESET_COLOR,
// PROP_ATTR_NAME_COLOR, RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// pfraction->min_num, pfraction->min_den, pfraction->max_num,
// pfraction->max_den, RESET_COLOR, PROP_ATTR_NAME_COLOR,
// RESET_COLOR, PROP_ATTR_VALUE_COLOR,
// gst_value_get_fraction_numerator (&value),
// gst_value_get_fraction_denominator (&value), RESET_COLOR);
// } else if (param->value_type == GST_TYPE_ARRAY) {
// GstParamSpecArray *parray = GST_PARAM_SPEC_ARRAY_LIST (param);
// if (parray->element_spec) {
// n_print ("%sGstValueArray of GValues of type%s %s\"%s\"%s",
// PROP_VALUE_COLOR, RESET_COLOR, DATATYPE_COLOR,
// g_type_name (parray->element_spec->value_type), RESET_COLOR);
// } else {
// n_print ("%sGstValueArray of GValues%s", PROP_VALUE_COLOR,
// RESET_COLOR);
// }
// } else {
// n_print ("%sUnknown type %ld%s %s\"%s\"%s", PROP_VALUE_COLOR,
// (glong) param->value_type, RESET_COLOR, DATATYPE_COLOR,
// g_type_name (param->value_type), RESET_COLOR);
// }
// break;
// }
// if (!readable)
// g_print (" %sWrite only%s\n", PROP_VALUE_COLOR, RESET_COLOR);
// else
// g_print ("\n");
// pop_indent_n (11);
// g_value_reset (&value);
// }
// if (num_properties == 0)
// n_print ("%snone%s\n", PROP_VALUE_COLOR, RESET_COLOR);
// pop_indent ();
// g_free (property_specs);
// }

100
gst/gst_app_sink.go Normal file
View File

@@ -0,0 +1,100 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0 gstreamer-app-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -Wno-unused-function -g
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include "gst.go.h"
*/
import "C"
import (
"bytes"
"errors"
"io"
"unsafe"
)
// AppSink wraps an Element object with additional methods for pulling samples.
type AppSink struct{ *Element }
// NewAppSink returns a new appsink element. Unref after usage.
func NewAppSink() (*AppSink, error) {
elem, err := NewElement("appsink")
if err != nil {
return nil, err
}
return wrapAppSink(elem), nil
}
// Instance returns the native GstAppSink instance.
func (a *AppSink) Instance() *C.GstAppSink { return C.toGstAppSink(a.unsafe()) }
// ErrEOS represents that the stream has ended.
var ErrEOS = errors.New("Pipeline has reached end-of-stream")
// IsEOS returns true if this AppSink has reached the end-of-stream.
func (a *AppSink) IsEOS() bool {
return gobool(C.gst_app_sink_is_eos((*C.GstAppSink)(a.Instance())))
}
// BlockPullSample will block until a sample becomes available or the stream
// is ended.
func (a *AppSink) BlockPullSample() (*Sample, error) {
for {
if a.IsEOS() {
return nil, ErrEOS
}
// This function won't block if the entire pipeline is waiting for data
sample := C.gst_app_sink_pull_sample((*C.GstAppSink)(a.Instance()))
if sample == nil {
continue
}
return NewSample(sample), nil
}
}
// PullSample will try to pull a sample or return nil if none is available.
func (a *AppSink) PullSample() (*Sample, error) {
if a.IsEOS() {
return nil, ErrEOS
}
sample := C.gst_app_sink_try_pull_sample(
(*C.GstAppSink)(a.Instance()),
C.GST_SECOND,
)
if sample != nil {
return NewSample(sample), nil
}
return nil, nil
}
// Sample is a go wrapper around a GstSample object.
type Sample struct {
sample *C.GstSample
}
// NewSample creates a new Sample from the given *GstSample.
func NewSample(sample *C.GstSample) *Sample { return &Sample{sample: sample} }
// Instance returns the underlying *GstSample instance.
func (s *Sample) Instance() *C.GstSample { return s.sample }
// Unref calls gst_sample_unref on the sample.
func (s *Sample) Unref() { C.gst_sample_unref((*C.GstSample)(s.Instance())) }
// GetBuffer returns a Reader for the buffer inside this sample.
func (s *Sample) GetBuffer() io.Reader {
buffer := C.gst_sample_get_buffer((*C.GstSample)(s.Instance()))
var mapInfo C.GstMapInfo
C.gst_buffer_map(
(*C.GstBuffer)(buffer),
(*C.GstMapInfo)(unsafe.Pointer(&mapInfo)),
C.GST_MAP_READ,
)
defer C.gst_buffer_unmap((*C.GstBuffer)(buffer), (*C.GstMapInfo)(unsafe.Pointer(&mapInfo)))
return bytes.NewBuffer(C.GoBytes(unsafe.Pointer(mapInfo.data), (C.int)(mapInfo.size)))
}
func wrapAppSink(elem *Element) *AppSink { return &AppSink{elem} }

84
gst/gst_app_src.go Normal file
View File

@@ -0,0 +1,84 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0 gstreamer-app-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include "gst.go.h"
*/
import "C"
import (
"io"
"io/ioutil"
"time"
"unsafe"
)
// AppSrc wraps an Element object with additional methods for pushing samples.
type AppSrc struct{ *Element }
// NewAppSrc returns a new AppSrc element.
func NewAppSrc() (*AppSrc, error) {
elem, err := NewElement("appsrc")
if err != nil {
return nil, err
}
return wrapAppSrc(elem), nil
}
// Instance returns the native GstAppSink instance.
func (a *AppSrc) Instance() *C.GstAppSrc { return C.toGstAppSrc(a.unsafe()) }
// SetSize sets the size of the source stream in bytes. You should call this for
// streams of fixed length.
func (a *AppSrc) SetSize(size int64) {
C.gst_app_src_set_size((*C.GstAppSrc)(a.Instance()), (C.gint64)(size))
}
// SetDuration sets the duration of the source stream. You should call
// this if the value is known.
func (a *AppSrc) SetDuration(dur time.Duration) {
C.gst_app_src_set_duration((*C.GstAppSrc)(a.Instance()), (C.ulong)(dur.Nanoseconds()))
}
// EndStream signals to the app source that the stream has ended after the last queued
// buffer.
func (a *AppSrc) EndStream() FlowReturn {
ret := C.gst_app_src_end_of_stream((*C.GstAppSrc)(a.Instance()))
return FlowReturn(ret)
}
// SetLive sets whether or not this is a live stream.
func (a *AppSrc) SetLive(b bool) error { return a.Set("is-live", b) }
// PushBuffer pushes a buffer to the appsrc. Currently by default this will block
// until the source is ready to accept the buffer.
func (a *AppSrc) PushBuffer(data io.Reader) FlowReturn {
out, err := ioutil.ReadAll(data)
if err != nil {
return FlowError
}
str := string(out)
p := unsafe.Pointer(C.CString(str))
defer C.free(p)
buf := C.gst_buffer_new_wrapped((C.gpointer)(p), C.ulong(len(str)))
ret := C.gst_app_src_push_buffer((*C.GstAppSrc)(a.Instance()), (*C.GstBuffer)(buf))
return FlowReturn(ret)
}
// FlowReturn is go type casting for GstFlowReturn.
type FlowReturn C.GstFlowReturn
// Type casting of the GstFlowReturn types. Custom ones are omitted for now.
const (
FlowOK FlowReturn = C.GST_FLOW_OK // Data passing was ok
FlowNotLinked = C.GST_FLOW_NOT_LINKED // Pad is not linked
FlowFlushing = C.GST_FLOW_FLUSHING // Pad is flushing
FlowEOS = C.GST_FLOW_EOS // Pad is EOS
FlowNotNegotiated = C.GST_FLOW_NOT_NEGOTIATED // Pad is not negotiated
FlowError = C.GST_FLOW_ERROR // Some (fatal) error occurred
FlowNotSupported = C.GST_FLOW_NOT_SUPPORTED // The operation is not supported.
)
func wrapAppSrc(elem *Element) *AppSrc { return &AppSrc{elem} }

72
gst/gst_bin.go Normal file
View File

@@ -0,0 +1,72 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
// Bin is a go wrapper arounds a GstBin.
type Bin struct{ *Element }
// Instance returns the underlying GstBin instance.
func (b *Bin) Instance() *C.GstBin { return C.toGstBin(b.unsafe()) }
// GetElementByName returns the element with the given name. Unref after usage.
func (b *Bin) GetElementByName(name string) (*Element, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
elem := C.gst_bin_get_by_name((*C.GstBin)(b.Instance()), (*C.gchar)(cName))
if elem == nil {
return nil, fmt.Errorf("Could not find element with name %s", name)
}
return wrapElement(elem), nil
}
// GetElements returns a list of the elements added to this pipeline. Unref
// elements after usage.
func (b *Bin) GetElements() ([]*Element, error) {
iterator := C.gst_bin_iterate_elements((*C.GstBin)(b.Instance()))
return iteratorToElementSlice(iterator)
}
// GetSourceElements returns a list of all the source elements in this pipeline. Unref
// elements after usafe.
func (b *Bin) GetSourceElements() ([]*Element, error) {
iterator := C.gst_bin_iterate_sources((*C.GstBin)(b.Instance()))
return iteratorToElementSlice(iterator)
}
// GetSinkElements returns a list of all the sink elements in this pipeline. Unref
// elements after usage.
func (b *Bin) GetSinkElements() ([]*Element, error) {
iterator := C.gst_bin_iterate_sinks((*C.GstBin)(b.Instance()))
return iteratorToElementSlice(iterator)
}
// Add wraps `gst_bin_add`.
func (b *Bin) Add(elem *Element) error {
if ok := C.gst_bin_add((*C.GstBin)(b.Instance()), (*C.GstElement)(elem.Instance())); !gobool(ok) {
return fmt.Errorf("Failed to add element to pipeline: %s", elem.Name())
}
return nil
}
// AddMany is a go implementation of `gst_bin_add_many` to compensate for the inability
// to use variadic functions in cgo.
func (b *Bin) AddMany(elems ...*Element) error {
for _, elem := range elems {
if err := b.Add(elem); err != nil {
return err
}
}
return nil
}
func wrapBin(bin *C.GstBin) *Bin { return &Bin{wrapElement(&bin.element)} }

78
gst/gst_bus.go Normal file
View File

@@ -0,0 +1,78 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import "sync"
// Bus is a Go wrapper around a GstBus. It provides convenience methods for
// popping messages from the queue.
type Bus struct {
*Object
msgChannels []chan *Message
mux sync.Mutex
}
// Instance returns the underlying GstBus instance.
func (b *Bus) Instance() *C.GstBus { return C.toGstBus(b.unsafe()) }
func (b *Bus) deliverMessages() {
for {
msg := b.BlockPopMessage()
if msg == nil {
return
}
b.mux.Lock()
for _, ch := range b.msgChannels {
ch <- msg.Ref()
}
b.mux.Unlock()
msg.Unref()
}
}
// MessageChan returns a new channel to listen for messages asynchronously. Messages
// should be unreffed after each usage. Messages are delivered to channels in the
// order in which this function was called.
//
// While a message is being delivered to created channels, there is a lock on creating
// new ones.
func (b *Bus) MessageChan() chan *Message {
b.mux.Lock()
defer b.mux.Unlock()
ch := make(chan *Message)
b.msgChannels = append(b.msgChannels, ch)
if len(b.msgChannels) == 1 {
go b.deliverMessages()
}
return ch
}
// BlockPopMessage blocks until a message is available on the bus and then returns it.
// This function can return nil if the bus is closed. The message should be unreffed
// after usage.
func (b *Bus) BlockPopMessage() *Message {
// I think this is ok since no other main loop is running
msg := C.gst_bus_poll(
(*C.GstBus)(b.Instance()),
C.GST_MESSAGE_ANY,
C.GST_CLOCK_TIME_NONE,
)
if msg == nil {
return nil
}
return wrapMessage(msg)
}
func wrapBus(bus *C.GstBus) *Bus {
return &Bus{
Object: wrapObject(&bus.object),
msgChannels: make([]chan *Message, 0),
}
}

106
gst/gst_caps.go Normal file
View File

@@ -0,0 +1,106 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include <glib-object.h>
#include "gst.go.h"
*/
import "C"
import (
"fmt"
"strings"
"unsafe"
"github.com/gotk3/gotk3/glib"
)
// Caps is a wrapper around GstCaps. It provides a function for easy type
// conversion.
type Caps []*Structure
// NewRawCaps returns new GstCaps with the given format, sample-rate, and channels.
func NewRawCaps(format string, rate, channels int) Caps {
return Caps{
{
Name: "audio/x-raw",
Data: map[string]interface{}{
"format": format,
"rate": rate,
"channels": channels,
},
},
}
}
// FromGstCaps converts a C GstCaps objects to a go type.
func FromGstCaps(caps *C.GstCaps) Caps {
out := make(Caps, 0)
size := int(C.gst_caps_get_size((*C.GstCaps)(caps)))
for i := 0; i < size-1; i++ {
s := C.gst_caps_get_structure((*C.GstCaps)(caps), (C.guint(i)))
out = append(out, FromGstStructure(s))
}
return out
}
// ToGstCaps returns the GstCaps representation of this Caps instance.
func (g Caps) ToGstCaps() *C.GstCaps {
// create a new empty caps object
caps := C.gst_caps_new_empty()
if caps == nil {
// extra nil check but this would only happen when larger issues are present
return nil
}
for _, st := range g {
// append the structure to the caps
C.gst_caps_append_structure((*C.GstCaps)(caps), (*C.GstStructure)(st.ToGstStructure()))
}
return caps
}
// Structure is a go implementation of a C GstStructure.
type Structure struct {
Name string
Data map[string]interface{}
}
// ToGstStructure converts this structure to a C GstStructure.
func (s *Structure) ToGstStructure() *C.GstStructure {
var structStr string
structStr = s.Name
// build a structure string from the data
if s.Data != nil {
elems := make([]string, 0)
for k, v := range s.Data {
elems = append(elems, fmt.Sprintf("%s=%v", k, v))
}
structStr = fmt.Sprintf("%s, %s", s.Name, strings.Join(elems, ", "))
}
// convert the structure string to a cstring
cstr := C.CString(structStr)
defer C.free(unsafe.Pointer(cstr))
// a small buffer for garbage
p := C.malloc(C.size_t(128))
defer C.free(p)
// create a structure from the string
cstruct := C.gst_structure_from_string((*C.gchar)(cstr), (**C.gchar)(p))
return cstruct
}
// FromGstStructure converts the given C GstStructure into a go structure.
func FromGstStructure(s *C.GstStructure) *Structure {
v := &Structure{}
v.Name = C.GoString((*C.char)(C.gst_structure_get_name((*C.GstStructure)(s))))
n := uint(C.gst_structure_n_fields(s))
v.Data = make(map[string]interface{})
for i := uint(0); i < n; i++ {
fn := C.gst_structure_nth_field_name((*C.GstStructure)(s), C.guint(i))
fv := glib.ValueFromNative(unsafe.Pointer(C.gst_structure_id_get_value((*C.GstStructure)(s), C.g_quark_from_string(fn))))
val, _ := fv.GoValue()
v.Data[C.GoString((*C.char)(fn))] = val
}
return v
}

22
gst/gst_clock.go Normal file
View File

@@ -0,0 +1,22 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"unsafe"
)
// Clock is a go wrapper around a GstClock.
type Clock struct{ *Object }
// Instance returns the underlying GstClock instance.
func (c *Clock) Instance() *C.GstClock { return C.toGstClock(c.unsafe()) }
func wrapClock(c *C.GstClock) *Clock {
return &Clock{wrapObject(C.toGstObject(unsafe.Pointer(c)))}
}

192
gst/gst_element.go Normal file
View File

@@ -0,0 +1,192 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"errors"
"fmt"
"unsafe"
"github.com/gotk3/gotk3/glib"
)
// Element is a Go wrapper around a GstElement.
type Element struct{ *Object }
// ElementLinkMany is a go implementation of `gst_element_link_many` to compensate for
// no variadic functions in cgo.
func ElementLinkMany(elems ...*Element) error {
for idx, elem := range elems {
if idx == 0 {
// skip the first one as the loop always links previous to current
continue
}
if err := elems[idx-1].Link(elem); err != nil {
return err
}
}
return nil
}
// 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())
}
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.ToGstCaps())); !gobool(ok) {
return fmt.Errorf("Failed to link %s to %s with provider caps", e.Name(), elem.Name())
}
return nil
}
// GetBus returns the GstBus for retrieving messages from this element.
func (e *Element) GetBus() (*Bus, error) {
bus := C.gst_element_get_bus((*C.GstElement)(e.Instance()))
if bus == nil {
return nil, errors.New("Could not retrieve bus from element")
}
return wrapBus(bus), nil
}
// 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 {
stateRet := C.gst_element_set_state((*C.GstElement)(e.Instance()), C.GST_STATE_PLAYING)
if stateRet == C.GST_STATE_CHANGE_FAILURE {
return fmt.Errorf("Failed to change state to %s", state.String())
}
var curState C.GstState
C.gst_element_get_state(
(*C.GstElement)(e.Instance()),
(*C.GstState)(unsafe.Pointer(&curState)),
(*C.GstState)(unsafe.Pointer(&state)),
C.GST_CLOCK_TIME_NONE,
)
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()))
if factory == nil {
return nil
}
return wrapElementFactory(factory)
}
// GetPads retrieves a list of pads associated with the element.
func (e *Element) GetPads() []*Pad {
goList := glib.WrapList(uintptr(unsafe.Pointer(e.Instance().pads)))
out := make([]*Pad, 0)
goList.Foreach(func(item interface{}) {
pt := item.(unsafe.Pointer)
out = append(out, wrapPad(C.toGstPad(pt)))
})
return out
}
// 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 {
glist := C.gst_element_get_pad_template_list((*C.GstElement)(e.Instance()))
if glist == nil {
return nil
}
goList := glib.WrapList(uintptr(unsafe.Pointer(glist)))
out := make([]*PadTemplate, 0)
goList.Foreach(func(item interface{}) {
pt := item.(unsafe.Pointer)
out = append(out, wrapPadTemplate(C.toGstPadTemplate(pt)))
})
return out
}
// GetClock returns the clock for this element or nil. Unref after usage.
func (e *Element) GetClock() *Clock {
clock := C.gst_element_get_clock((*C.GstElement)(e.Instance()))
if clock == nil {
return nil
}
return wrapClock(clock)
}
// 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)))
}
// IsURIHandler returns true if this element can handle URIs.
func (e *Element) IsURIHandler() bool {
return gobool(C.gstElementIsURIHandler(e.Instance()))
}
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() {
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)
}
func wrapElement(elem *C.GstElement) *Element {
return &Element{wrapObject(&elem.object)}
}
// ElementFlags casts C GstElementFlags to a go type
type ElementFlags C.GstElementFlags
// Type casting of element flags
const (
ElementFlagLockedState ElementFlags = C.GST_ELEMENT_FLAG_LOCKED_STATE // (16) ignore state changes from parent
ElementFlagSink = C.GST_ELEMENT_FLAG_SINK // (32) the element is a sink
ElementFlagSource = C.GST_ELEMENT_FLAG_SOURCE // (64) the element is a source.
ElementFlagProvideClock = C.GST_ELEMENT_FLAG_PROVIDE_CLOCK // (128) the element can provide a clock
ElementFlagRequireClock = C.GST_ELEMENT_FLAG_REQUIRE_CLOCK // (256) the element requires a clock
ElementFlagIndexable = C.GST_ELEMENT_FLAG_INDEXABLE // (512) the element can use an index
ElementFlagLast = C.GST_ELEMENT_FLAG_LAST // (16384) offset to define more flags
)

101
gst/gst_element_factory.go Normal file
View File

@@ -0,0 +1,101 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
// NewElement is a generic wrapper around `gst_element_factory_make`.
func NewElement(name string) (*Element, error) {
elemName := C.CString(name)
defer C.free(unsafe.Pointer(elemName))
elem := C.gst_element_factory_make((*C.gchar)(elemName), nil)
if elem == nil {
return nil, fmt.Errorf("Could not create element: %s", name)
}
return wrapElement(elem), nil
}
// NewElementMany is a convenience wrapper around building many GstElements in a
// single function call. It returns an error if the creation of any element fails. A
// map containing the ordinal of the argument to the Element created is returned.
func NewElementMany(elemNames ...string) (map[int]*Element, error) {
elemMap := make(map[int]*Element)
for idx, name := range elemNames {
elem, err := NewElement(name)
if err != nil {
return nil, err
}
elemMap[idx] = elem
}
return elemMap, nil
}
// ElementFactory wraps the GstElementFactory
type ElementFactory struct{ *Object }
// Find returns the factory for the given plugin, or nil if it doesn't exist. Unref after usage.
func Find(name string) *ElementFactory {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
factory := C.gst_element_factory_find((*C.gchar)(cName))
if factory == nil {
return nil
}
return wrapElementFactory(factory)
}
// Instance returns the C GstFactory instance
func (e *ElementFactory) Instance() *C.GstElementFactory { return C.toGstElementFactory(e.unsafe()) }
// CanSinkAllCaps checks if the factory can sink all possible capabilities.
func (e *ElementFactory) CanSinkAllCaps(caps *C.GstCaps) bool {
return gobool(C.gst_element_factory_can_sink_all_caps((*C.GstElementFactory)(e.Instance()), (*C.GstCaps)(caps)))
}
// CanSinkAnyCaps checks if the factory can sink any possible capability.
func (e *ElementFactory) CanSinkAnyCaps(caps *C.GstCaps) bool {
return gobool(C.gst_element_factory_can_sink_any_caps((*C.GstElementFactory)(e.Instance()), (*C.GstCaps)(caps)))
}
// CanSourceAllCaps checks if the factory can src all possible capabilities.
func (e *ElementFactory) CanSourceAllCaps(caps *C.GstCaps) bool {
return gobool(C.gst_element_factory_can_src_all_caps((*C.GstElementFactory)(e.Instance()), (*C.GstCaps)(caps)))
}
// CanSourceAnyCaps checks if the factory can src any possible capability.
func (e *ElementFactory) CanSourceAnyCaps(caps *C.GstCaps) bool {
return gobool(C.gst_element_factory_can_src_any_caps((*C.GstElementFactory)(e.Instance()), (*C.GstCaps)(caps)))
}
// GetMetadata gets the metadata on this factory with key.
func (e *ElementFactory) GetMetadata(key string) string {
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
res := C.gst_element_factory_get_metadata((*C.GstElementFactory)(e.Instance()), (*C.gchar)(ckey))
defer C.free(unsafe.Pointer(res))
return C.GoString(res)
}
// GetMetadataKeys gets the available keys for the metadata on this factory.
func (e *ElementFactory) GetMetadataKeys() []string {
keys := C.gst_element_factory_get_metadata_keys((*C.GstElementFactory)(e.Instance()))
if keys == nil {
return nil
}
defer C.g_strfreev(keys)
size := C.sizeOfGCharArray(keys)
return goStrings(size, keys)
}
func wrapElementFactory(factory *C.GstElementFactory) *ElementFactory {
return &ElementFactory{wrapObject(C.toGstObject(unsafe.Pointer(factory)))}
}

156
gst/gst_message.go Normal file
View File

@@ -0,0 +1,156 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"strings"
"unsafe"
)
// Message is a Go wrapper around a GstMessage. It provides convenience methods for
// unref-ing and parsing the underlying messages.
type Message struct {
msg *C.GstMessage
}
// wrapMessage returns a new Message from the given GstMessage.
func wrapMessage(msg *C.GstMessage) *Message { return &Message{msg: msg} }
// Native returns the underlying GstMessage object.
func (m *Message) Native() *C.GstMessage { return C.toGstMessage(unsafe.Pointer(m.msg)) }
// Type returns the MessageType of the message.
func (m *Message) Type() MessageType {
return MessageType(m.Native()._type)
}
// TypeName returns a Go string of the GstMessageType name.
func (m *Message) TypeName() string {
return C.GoString(C.gst_message_type_get_name((C.GstMessageType)(m.Type())))
}
// getStructure returns the GstStructure in this message, using the type of the message
// to determine the method to use.
func (m *Message) getStructure() map[string]string {
var st *C.GstStructure
switch m.Type() {
case MessageError:
C.gst_message_parse_error_details((*C.GstMessage)(m.Native()), (**C.GstStructure)(unsafe.Pointer(&st)))
case MessageInfo:
C.gst_message_parse_info_details((*C.GstMessage)(m.Native()), (**C.GstStructure)(unsafe.Pointer(&st)))
case MessageWarning:
C.gst_message_parse_warning_details((*C.GstMessage)(m.Native()), (**C.GstStructure)(unsafe.Pointer(&st)))
}
// if no structure was returned, immediately return nil
if st == nil {
return nil
}
// The returned structure must not be freed. Applies to all methods.
// https://gstreamer.freedesktop.org/documentation/gstreamer/gstmessage.html#gst_message_parse_error_details
return structureToGoMap(st)
}
// parseToError returns a new GoGError from this message instance. There are multiple
// message types that parse to this interface.
func (m *Message) parseToError() *GoGError {
var gerr *C.GError
var debugInfo *C.gchar
switch m.Type() {
case MessageError:
C.gst_message_parse_error((*C.GstMessage)(m.Native()), (**C.GError)(unsafe.Pointer(&gerr)), (**C.gchar)(unsafe.Pointer(&debugInfo)))
case MessageInfo:
C.gst_message_parse_info((*C.GstMessage)(m.Native()), (**C.GError)(unsafe.Pointer(&gerr)), (**C.gchar)(unsafe.Pointer(&debugInfo)))
case MessageWarning:
C.gst_message_parse_warning((*C.GstMessage)(m.Native()), (**C.GError)(unsafe.Pointer(&gerr)), (**C.gchar)(unsafe.Pointer(&debugInfo)))
}
// if error was nil return immediately
if gerr == nil {
return nil
}
// cleanup the C error immediately and let the garbage collector
// take over from here.
defer C.g_error_free((*C.GError)(gerr))
defer C.g_free((C.gpointer)(debugInfo))
return &GoGError{
errMsg: C.GoString(gerr.message),
details: m.getStructure(),
debugStr: strings.TrimSpace(C.GoString((*C.gchar)(debugInfo))),
}
}
// ParseInfo is identical to ParseError. The returned types are the same. However,
// this is intended for use with GstMessageType `GST_MESSAGE_INFO`.
func (m *Message) ParseInfo() *GoGError {
return m.parseToError()
}
// ParseWarning is identical to ParseError. The returned types are the same. However,
// this is intended for use with GstMessageType `GST_MESSAGE_WARNING`.
func (m *Message) ParseWarning() *GoGError {
return m.parseToError()
}
// ParseError will return a GoGError from the contents of this message. This will only work
// if the GstMessageType is `GST_MESSAGE_ERROR`.
func (m *Message) ParseError() *GoGError {
return m.parseToError()
}
// ParseStateChanged will return the old and new states as Go strings. This will only work
// if the GstMessageType is `GST_MESSAGE_STATE_CHANGED`.
func (m *Message) ParseStateChanged() (oldState, newState State) {
var gOldState, gNewState C.GstState
C.gst_message_parse_state_changed((*C.GstMessage)(m.Native()), (*C.GstState)(unsafe.Pointer(&gOldState)), (*C.GstState)(unsafe.Pointer(&gNewState)), nil)
oldState = State(gOldState)
newState = State(gNewState)
return
}
// Unref will call `gst_message_unref` on the underlying GstMessage, freeing it from memory.
func (m *Message) Unref() { C.gst_message_unref((*C.GstMessage)(m.Native())) }
// Ref will increase the ref count on this message. This increases the total amount of times
// Unref needs to be called before the object is freed from memory. It returns the underlying
// message object for convenience.
func (m *Message) Ref() *Message {
C.gst_message_ref((*C.GstMessage)(m.Native()))
return m
}
// Copy will copy this object into a new Message that can be Unrefed separately.
func (m *Message) Copy() *Message {
newNative := C.gst_message_copy((*C.GstMessage)(m.Native()))
return wrapMessage(newNative)
}
// GoGError is a Go wrapper for a C GError. It implements the error interface
// and provides additional functions for retrieving debug strings and details.
type GoGError struct {
errMsg, debugStr string
details map[string]string
}
// Message is an alias to `Error()`. It's for clarity when this object
// is parsed from a `GST_MESSAGE_INFO` or `GST_MESSAGE_WARNING`.
func (e *GoGError) Message() string { return e.Error() }
// Error implements the error interface and returns the error message.
func (e *GoGError) Error() string { return e.errMsg }
// DebugString returns any debug info alongside the error.
func (e *GoGError) DebugString() string { return e.debugStr }
// Details contains additional metadata about the error if available.
func (e *GoGError) Details() map[string]string { return e.details }

192
gst/gst_object.go Normal file
View File

@@ -0,0 +1,192 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"strings"
"unsafe"
"github.com/gotk3/gotk3/glib"
)
// Object is a go representation of a GstObject. Type casting stops here
// and we do not descend into the glib library.
type Object struct{ *glib.InitiallyUnowned }
// native returns the pointer to the underlying object.
func (o *Object) unsafe() unsafe.Pointer { return unsafe.Pointer(o.InitiallyUnowned.Native()) }
// Instance returns the native C GstObject.
func (o *Object) Instance() *C.GstObject { return C.toGstObject(o.unsafe()) }
// Class returns the GObjectClass of this instance.
func (o *Object) Class() *C.GObjectClass { return C.getGObjectClass(o.unsafe()) }
// Name returns the name of this object.
func (o *Object) Name() string {
cName := C.gst_object_get_name((*C.GstObject)(o.Instance()))
defer C.free(unsafe.Pointer(cName))
return C.GoString(cName)
}
// Interfaces returns the interfaces associated with this object.
func (o *Object) Interfaces() []string {
var size C.guint
ifaces := C.g_type_interfaces(C.ulong(o.TypeFromInstance()), &size)
if int(size) == 0 {
return nil
}
defer C.g_free((C.gpointer)(ifaces))
out := make([]string, int(size))
for _, t := range (*[1 << 30]int)(unsafe.Pointer(ifaces))[:size:size] {
out = append(out, glib.Type(t).Name())
}
return out
}
// ListProperties returns a list of the properties associated with this object.
// The default values assumed in the parameter spec reflect the values currently
// set in this object.
func (o *Object) ListProperties() []*ParameterSpec {
var size C.guint
props := C.g_object_class_list_properties((*C.GObjectClass)(o.Class()), &size)
if props == nil {
return nil
}
defer C.g_free((C.gpointer)(props))
out := make([]*ParameterSpec, 0)
for _, prop := range (*[1 << 30]*C.GParamSpec)(unsafe.Pointer(props))[:size:size] {
var gval C.GValue
flags := ParameterFlags(prop.flags)
if flags.Has(ParameterReadable) {
C.g_object_get_property((*C.GObject)(o.unsafe()), prop.name, &gval)
} else {
C.g_param_value_set_default((*C.GParamSpec)(prop), &gval)
}
out = append(out, &ParameterSpec{
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)),
})
}
return out
}
func wrapObject(o *C.GstObject) *Object {
obj := &Object{&glib.InitiallyUnowned{Object: glib.Take(unsafe.Pointer(o))}}
obj.RefSink()
return obj
}
// ParameterSpec is a go representation of a C GParamSpec
type ParameterSpec struct {
param *C.GParamSpec
Name string
Blurb string
Flags ParameterFlags
ValueType glib.Type
OwnerType glib.Type
DefaultValue *glib.Value
}
// ParameterFlags is a go cast of GParamFlags.
type ParameterFlags C.GParamFlags
// Has returns true if these flags contain the provided ones.
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
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
ParameterStaticName = C.G_PARAM_STATIC_NAME // the string used as name when constructing the parameter is guaranteed to remain valid and unmodified for the lifetime of the parameter. Since 2.8
ParameterStaticNick = C.G_PARAM_STATIC_NICK // the string used as nick when constructing the parameter is guaranteed to remain valid and unmmodified for the lifetime of the parameter. Since 2.8
ParameterStaticBlurb = C.G_PARAM_STATIC_BLURB // the string used as blurb when constructing the parameter is guaranteed to remain valid and unmodified for the lifetime of the parameter. Since 2.8
ParameterExplicitNotify = C.G_PARAM_EXPLICIT_NOTIFY // calls to g_object_set_property() for this property will not automatically result in a "notify" signal being emitted: the implementation must call g_object_notify() themselves in case the property actually changes. Since: 2.42.
ParameterDeprecated = C.G_PARAM_DEPRECATED // the parameter is deprecated and will be removed in a future version. A warning will be generated if it is used while running with G_ENABLE_DIAGNOSTIC=1. Since 2.26
ParameterControllable = C.GST_PARAM_CONTROLLABLE
ParameterMutablePlaying = C.GST_PARAM_MUTABLE_PLAYING
ParameterMutablePaused = C.GST_PARAM_MUTABLE_PAUSED
ParameterMutableReady = C.GST_PARAM_MUTABLE_READY
)
var allFlags = []ParameterFlags{
ParameterReadable,
ParameterWritable,
ParameterConstruct,
ParameterConstructOnly,
ParameterLaxValidation,
ParameterStaticName,
ParameterStaticNick,
ParameterStaticBlurb,
ParameterExplicitNotify,
ParameterDeprecated,
ParameterControllable,
ParameterMutablePlaying,
ParameterMutablePaused,
ParameterMutableReady,
}
var allFlagStrings = []string{
"readable",
"writable",
"construct",
"construct only",
"lax validation",
"static name",
"static nick",
"static blurb",
"explicity notify",
"deprecated",
"controllable",
"changeable in NULL, READY, PAUSED or PLAYING state",
"changeable only in NULL, READY or PAUSED state",
"changeable only in NULL or READY state",
}
func (p ParameterFlags) String() string {
out := make([]string, 0)
for idx, param := range allFlags {
if p.Has(param) {
out = append(out, allFlagStrings[idx])
}
}
return strings.Join(out, ", ")
}
// GstFlagsString returns a string of the flags that are relevant specifically
// to gstreamer.
func (p ParameterFlags) GstFlagsString() string {
out := make([]string, 0)
if p.Has(ParameterReadable) {
out = append(out, "readable")
}
if p.Has(ParameterWritable) {
out = append(out, "writable")
}
if p.Has(ParameterControllable) {
out = append(out, "controllable")
}
if p.Has(ParameterMutablePlaying) {
out = append(out, "changeable in NULL, READY, PAUSED or PLAYING state")
}
if p.Has(ParameterMutablePaused) {
out = append(out, "changeable only in NULL, READY or PAUSED state")
}
if p.Has(ParameterMutableReady) {
out = append(out, "changeable only in NULL or READY state")
}
return strings.Join(out, ", ")
}

106
gst/gst_pad.go Normal file
View File

@@ -0,0 +1,106 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import "unsafe"
// Pad is a go representation of a GstPad
type Pad struct{ *Object }
// Instance returns the underlying C GstPad.
func (p *Pad) Instance() *C.GstPad { return C.toGstPad(p.unsafe()) }
// Direction returns the direction of this pad.
func (p *Pad) Direction() PadDirection {
return PadDirection(C.gst_pad_get_direction((*C.GstPad)(p.Instance())))
}
// Template returns the template for this pad or nil.
func (p *Pad) Template() *PadTemplate { return wrapPadTemplate(p.Instance().padtemplate) }
// CurrentCaps returns the caps for this Pad or nil.
func (p *Pad) CurrentCaps() Caps {
caps := C.gst_pad_get_current_caps((*C.GstPad)(p.Instance()))
if caps == nil {
return nil
}
defer C.gst_caps_unref(caps)
return FromGstCaps(caps)
}
func wrapPad(p *C.GstPad) *Pad {
return &Pad{wrapObject(C.toGstObject(unsafe.Pointer(p)))}
}
// PadTemplate is a go representation of a GstPadTemplate
type PadTemplate struct{ *Object }
// Instance returns the underlying C GstPadTemplate.
func (p *PadTemplate) Instance() *C.GstPadTemplate { return C.toGstPadTemplate(p.unsafe()) }
// Name returns the name of the pad template.
func (p *PadTemplate) Name() string { return C.GoString(p.Instance().name_template) }
// Direction returns the direction of the pad template.
func (p *PadTemplate) Direction() PadDirection { return PadDirection(p.Instance().direction) }
// Presence returns the presence of the pad template.
func (p *PadTemplate) Presence() PadPresence { return PadPresence(p.Instance().presence) }
// Caps returns the caps of the pad template.
func (p *PadTemplate) Caps() Caps { return FromGstCaps(p.Instance().caps) }
func wrapPadTemplate(p *C.GstPadTemplate) *PadTemplate {
return &PadTemplate{wrapObject(C.toGstObject(unsafe.Pointer(p)))}
}
// PadDirection is a cast of GstPadDirection to a go type.
type PadDirection C.GstPadDirection
// Type casting of pad directions
const (
PadUnknown PadDirection = C.GST_PAD_UNKNOWN // (0) - the direction is unknown
PadSource = C.GST_PAD_SRC // (1) - the pad is a source pad
PadSink = 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"
}
return ""
}
// PadPresence is a cast of GstPadPresence to a go type.
type PadPresence C.GstPadPresence
// Type casting of pad presences
const (
PadAlways PadPresence = C.GST_PAD_ALWAYS // (0) - the pad is always available
PadSometimes = C.GST_PAD_SOMETIMES // (1) - the pad will become available depending on the media stream
PadRequest = 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"
}
return ""
}

528
gst/gst_pipeline.go Normal file
View File

@@ -0,0 +1,528 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -Wno-incompatible-pointer-types -g
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
"unsafe"
)
// PipelineFlags represents arguments passed to a new Pipeline.
type PipelineFlags int
const (
// PipelineInternalOnly signals that this pipeline only handles data internally.
PipelineInternalOnly PipelineFlags = 1 << iota
// PipelineRead signals that the Read() method can be used on the end of this pipeline.
PipelineRead
// PipelineWrite signals that the Write() method can be used on the start of this pipeline.
PipelineWrite
// PipelineUseGstApp signals the desire to use an AppSink or AppSrc instead of the default
// os pipes, fdsrc, and fdsink.
// When using this flag, you should interact with the pipeline using the GetAppSink and
// GetAppSrc methods.
PipelineUseGstApp
// PipelineReadWrite signals that this pipeline can be both read and written to.
PipelineReadWrite = PipelineRead | PipelineWrite
)
// has returns true if these flags contain the given flag.
func (p PipelineFlags) has(b PipelineFlags) bool { return p&b != 0 }
// State is a type cast of the C GstState
type State int
// Type casting for GstStates
const (
VoidPending State = C.GST_STATE_VOID_PENDING // (0) no pending state.
StateNull = C.GST_STATE_NULL // (1) the NULL state or initial state of an element.
StateReady = C.GST_STATE_READY // (2) the element is ready to go to PAUSED.
StatePaused = C.GST_STATE_PAUSED // (3) the element is PAUSED, it is ready to accept and process data. Sink elements however only accept one buffer and then block.
StatePlaying = C.GST_STATE_PLAYING // (4) the element is PLAYING, the GstClock is running and the data is flowing.
)
func (s State) String() string {
return C.GoString(C.gst_element_state_get_name((C.GstState)(s)))
}
// Pipeline is the base implementation of a GstPipeline using CGO to wrap
// gstreamer API calls. It provides methods to be inherited by the extending
// PlaybackPipeline and RecordingPipeline objects. The struct itself implements
// a ReadWriteCloser.
type Pipeline struct {
*Bin
// a local reference to the bus so duplicates aren't created
// when retrieved by the user
bus *Bus
// The buffers backing the Read and Write methods
destBuf *bufio.Reader
srcBuf *bufio.Writer
// used with PipelineWrite
srcReader, srcWriter *os.File
// used with PipelineRead
destReader, destWriter *os.File
// used with PipelineWrite AND PipelineGstApp
appSrc *AppSrc
// used with PipelineRead AND PipelineGstApp
appSink *AppSink
autoFlush bool // when set to true, the contents of the app sink are automatically flushed to the read buffer.
// The element that represents the source/dest pipeline
// and any caps to apply to it.
srcElement *Element
srcCaps Caps
destElement *Element
// whether or not the pipeline was built from a string. this is checked when
// starting to see who is responsible for build and linking the buffers.
pipelineFromHelper bool
// A channel where a caller can listen for errors asynchronously.
errCh chan error
// A channel where a caller can listen for messages
msgCh []chan *Message
}
func newEmptyPipeline() (*C.GstPipeline, error) {
pipeline := C.gst_pipeline_new((*C.gchar)(nil))
if pipeline == nil {
return nil, errors.New("Could not create new pipeline")
}
return C.toGstPipeline(unsafe.Pointer(pipeline)), nil
}
func newPipelineFromString(launchv string) (*C.GstPipeline, error) {
if len(strings.Split(launchv, "!")) < 2 {
return nil, fmt.Errorf("Given string is too short for a pipeline: %s", launchv)
}
cLaunchv := C.CString(launchv)
defer C.free(unsafe.Pointer(cLaunchv))
var gerr *C.GError
pipeline := C.gst_parse_launch((*C.gchar)(cLaunchv), (**C.GError)(&gerr))
if gerr != nil {
defer C.g_error_free((*C.GError)(gerr))
errMsg := C.GoString(gerr.message)
return nil, errors.New(errMsg)
}
return C.toGstPipeline(unsafe.Pointer(pipeline)), nil
}
// NewPipeline builds and returns a new empty Pipeline instance.
func NewPipeline(flags PipelineFlags) (*Pipeline, error) {
pipelineElement, err := newEmptyPipeline()
if err != nil {
return nil, err
}
pipeline := wrapPipeline(pipelineElement)
if err := applyFlags(pipeline, flags); err != nil {
return nil, err
}
return pipeline, nil
}
func applyFlags(pipeline *Pipeline, flags PipelineFlags) error {
// If the user wants to be able to write to the pipeline, set up the
// write-buffers
if flags.has(PipelineWrite) {
// Set up a pipe
if err := pipeline.setupWriters(); err != nil {
return err
}
}
// If the user wants to be able to read from the pipeline, setup the
// read-buffers.
if flags.has(PipelineRead) {
if err := pipeline.setupReaders(); err != nil {
return err
}
}
return nil
}
func wrapPipeline(elem *C.GstPipeline) *Pipeline { return &Pipeline{Bin: wrapBin(&elem.bin)} }
func (p *Pipeline) setupWriters() error {
var err error
p.srcReader, p.srcWriter, err = os.Pipe()
if err != nil {
return err
}
p.srcBuf = bufio.NewWriter(p.srcWriter)
return nil
}
func (p *Pipeline) setupReaders() error {
var err error
p.destReader, p.destWriter, err = os.Pipe()
if err != nil {
return err
}
p.destBuf = bufio.NewReader(p.destReader)
return nil
}
// Instance returns the native GstPipeline instance.
func (p *Pipeline) Instance() *C.GstPipeline { return C.toGstPipeline(p.unsafe()) }
// Read implements a Reader and returns data from the read buffer.
func (p *Pipeline) Read(b []byte) (int, error) {
if p.destBuf == nil {
return 0, io.ErrClosedPipe
}
return p.destBuf.Read(b)
}
// readerFd returns the file descriptor for the read buffer, or 0 if
// there isn't one. It returns the file descriptor that can be written to
// by gstreamer.
func (p *Pipeline) readerFd() uintptr {
if p.destWriter == nil {
return 0
}
return p.destWriter.Fd()
}
// Write implements a Writer and places data in the write buffer.
func (p *Pipeline) Write(b []byte) (int, error) {
if p.srcBuf == nil {
return 0, io.ErrClosedPipe
}
return p.srcBuf.Write(b)
}
// writerFd returns the file descriptor for the write buffer, or 0 if
// there isn't one. It returns the file descriptor that can be read from
// by gstreamer.
func (p *Pipeline) writerFd() uintptr {
if p.srcWriter == nil {
return 0
}
return p.srcReader.Fd()
}
// SetWriterCaps sets the caps on the write-buffer. You will usually want to call this
// on a custom pipeline, unless you are using downstream elements that do dynamic pad
// linking.
func (p *Pipeline) SetWriterCaps(caps Caps) { p.srcCaps = caps }
// LinkWriterTo links the write buffer on this Pipeline to the given element. This must
// be called when the pipeline is constructed with PipelineWrite or PipelineReadWrite.
func (p *Pipeline) LinkWriterTo(elem *Element) { p.srcElement = elem }
// LinkReaderTo links the read buffer on this Pipeline to the given element. This must
// be called when the pipeline is constructed with PipelineRead or PipelineReadWrite.
func (p *Pipeline) LinkReaderTo(elem *Element) { p.destElement = elem }
// IsUsingGstApp returns true if the current pipeline is using GstApp instead of file descriptors.
func (p *Pipeline) IsUsingGstApp() bool {
return p.appSrc != nil || p.appSink != nil
}
// GetAppSrc returns the AppSrc for this pipeline if created with PipelineUseGstApp.
// Unref after usage.
func (p *Pipeline) GetAppSrc() *AppSrc {
if p.appSrc == nil {
return nil
}
// increases the ref count on the element
return wrapAppSrc(p.appSrc.Element)
}
// GetAppSink returns the AppSink for this pipeline if created with PipelineUseGstApp.
// Unref after usage.
func (p *Pipeline) GetAppSink() *AppSink {
if p.appSink == nil {
return nil
}
// increases the ref count
return wrapAppSink(p.appSink.Element)
}
// GetBus returns the message bus for this pipeline.
func (p *Pipeline) GetBus() *Bus {
if p.bus == nil {
cBus := C.gst_pipeline_get_bus((*C.GstPipeline)(p.Instance()))
p.bus = wrapBus(cBus)
}
return p.bus
}
// SetAutoFlush sets whether or not samples should be automatically flushed to the read-buffer
// (default for pipelines not built with PipelineUseGstApp) and if messages should be flushed
// on the bus when the pipeline is stopped.
func (p *Pipeline) SetAutoFlush(b bool) {
p.Set("auto-flush-bus", b)
p.autoFlush = b
}
// AutoFlush returns true if the pipeline is using a GstAppSink and is configured to autoflush to the
// read-buffer.
func (p *Pipeline) AutoFlush() bool { return p.IsUsingGstApp() && p.autoFlush }
// Flush flushes the app sink to the read buffer. It is usually more desirable to interface
// with the PullSample and BlockPullSample methods on the AppSink interface directly. Or
// to set autoflush to true.
func (p *Pipeline) Flush() error {
sample, err := p.appSink.PullSample()
if err != nil { // err signals end of stream
return err
}
if sample == nil {
return nil
}
defer sample.Unref()
if _, err := io.Copy(p.destWriter, sample.GetBuffer()); err != nil {
return err
}
return nil
}
// BlockFlush is like Flush but it blocks until a sample is available. This is intended for
// use with PipelineUseGstApp.
func (p *Pipeline) BlockFlush() error {
sample, err := p.appSink.BlockPullSample()
if err != nil { // err signals end of stream
return err
}
if sample == nil {
return nil
}
defer sample.Unref()
if _, err := io.Copy(p.destWriter, sample.GetBuffer()); err != nil {
return err
}
return nil
}
// setupSrc sets up a source element with the given configuration.
func (p *Pipeline) setupSrc(pluginName string, args map[string]interface{}) (*Element, error) {
elem, err := NewElement(pluginName)
if err != nil {
return nil, err
}
for k, v := range args {
if err := elem.Set(k, v); err != nil {
return nil, err
}
}
if err := p.Add(elem); err != nil {
return nil, err
}
if p.srcCaps != nil {
return elem, elem.LinkFiltered(p.srcElement, p.srcCaps)
}
return elem, elem.Link(p.srcElement)
}
// setupFdSrc will setup a fdsrc as the source of the pipeline.
func (p *Pipeline) setupFdSrc() error {
_, err := p.setupSrc("fdsrc", map[string]interface{}{
"fd": p.writerFd(),
})
return err
}
// setupAppSrc sets up an appsrc as the source of the pipeline
func (p *Pipeline) setupAppSrc() error {
appSrc, err := p.setupSrc("appsrc", map[string]interface{}{
"block": true, // TODO: make this configurable
"emit-signals": false, // https://gstreamer.freedesktop.org/documentation/app/appsrc.html?gi-language=c
})
if err != nil {
return err
}
p.appSrc = &AppSrc{appSrc}
return nil
}
// setupSrcElement will setup the source element when the pipeline is constructed with
// PipelineWrite.
func (p *Pipeline) setupSrcElement() error {
if p.srcElement == nil {
return errors.New("Pipeline was constructed with PipelineWrite but LinkWriterTo was never called")
}
if p.IsUsingGstApp() {
return p.setupAppSrc()
}
return p.setupFdSrc()
}
// setupSink sets up a sink element with the given congifuration.
func (p *Pipeline) setupSink(pluginName string, args map[string]interface{}) (*Element, error) {
elem, err := NewElement(pluginName)
if err != nil {
return nil, err
}
for k, v := range args {
if err := elem.Set(k, v); err != nil {
return nil, err
}
}
if err := p.Add(elem); err != nil {
return nil, err
}
return elem, p.destElement.Link(elem)
}
// setupFdSink sets up a fdsink as the sink of the pipeline.
func (p *Pipeline) setupFdSink() error {
_, err := p.setupSink("fdsink", map[string]interface{}{
"fd": p.readerFd(),
})
return err
}
// setupAppSink sets up an appsink as the sink of the pipeline.
func (p *Pipeline) setupAppSink() error {
appSink, err := p.setupSink("appsink", map[string]interface{}{
"emit-signals": false,
})
if err != nil {
return err
}
p.appSink = wrapAppSink(appSink)
return nil
}
// setupDestElement will setup the destination (sink) element when the pipeline is constructed with
// PipelineRead.
func (p *Pipeline) setupDestElement() error {
if p.destElement == nil {
return errors.New("Pipeline was constructed with PipelineRead but LinkReaderTo was never called")
}
if p.IsUsingGstApp() {
return p.setupAppSink()
}
return p.setupFdSink()
}
// Start will start the GstPipeline. It is asynchronous so it does not need to be
// called within a goroutine, however, it is still safe to do so.
func (p *Pipeline) Start() error {
// If there is a write buffer on this pipeline, set up an fdsrc
if p.srcBuf != nil && !p.pipelineFromHelper {
if err := p.setupSrcElement(); err != nil {
return err
}
}
// If there is a read buffer on this pipeline, set up an fdsink
if p.destBuf != nil && !p.pipelineFromHelper {
if err := p.setupDestElement(); err != nil {
return err
}
}
return p.startPipeline()
}
func (p *Pipeline) closeBuffers() error {
if p.srcBuf != nil && p.srcReader != nil && p.srcWriter != nil {
if err := p.srcReader.Close(); err != nil {
return err
}
if err := p.srcWriter.Close(); err != nil {
return err
}
p.srcBuf = nil
}
if p.destBuf != nil && p.destReader != nil && p.destWriter != nil {
if err := p.destReader.Close(); err != nil {
return err
}
if err := p.destWriter.Close(); err != nil {
return err
}
p.destBuf = nil
}
return nil
}
// ReadBufferSize returns the current size of the unread portion of the read-buffer.
func (p *Pipeline) ReadBufferSize() int {
if p.destBuf == nil {
return 0
}
return p.destBuf.Buffered()
}
// WriteBufferSize returns the current size of the unread portion of the write-buffer.
func (p *Pipeline) WriteBufferSize() int {
if p.srcBuf == nil {
return 0
}
return p.srcBuf.Buffered()
}
// TotalBufferSize returns the sum of the Read and Write buffer unread portions.
func (p *Pipeline) TotalBufferSize() int { return p.WriteBufferSize() + p.ReadBufferSize() }
// Close implements a Closer and closes all buffers.
func (p *Pipeline) Close() error {
defer p.Unref()
if err := p.closeBuffers(); err != nil {
return err
}
return p.SetState(StateNull)
}
// startPipeline will set the GstPipeline to the PLAYING state.
func (p *Pipeline) startPipeline() error {
if err := p.SetState(StatePlaying); err != nil {
return err
}
// If using GstApp with autoflush
if p.AutoFlush() {
go func() {
for {
if err := p.BlockFlush(); err != nil {
// err signals end of stream
return
}
}
}()
}
return nil
}
// Wait waits for the given pipeline to reach end of stream.
func Wait(p *Pipeline) {
if p.Instance() == nil {
return
}
msgCh := p.GetBus().MessageChan()
for {
select {
default:
if p.Instance() == nil || p.GetState() == StateNull {
return
}
case msg := <-msgCh:
defer msg.Unref()
switch msg.Type() {
case MessageEOS:
return
}
}
}
}

194
gst/gst_pipeline_config.go Normal file
View File

@@ -0,0 +1,194 @@
package gst
import "fmt"
// PipelineConfig represents a list of elements and their configurations
// to be used with NewPipelineFromConfig.
type PipelineConfig struct {
Elements []*PipelineElement
}
// GetElementByName returns the Element configuration for the given name.
func (p *PipelineConfig) GetElementByName(name string) *PipelineElement {
for _, elem := range p.Elements {
if name == elem.GetName() {
return elem
}
}
return nil
}
// ElementNames returns a string slice of the names of all the plugins.
func (p *PipelineConfig) ElementNames() []string {
names := make([]string, 0)
for _, elem := range p.Elements {
names = append(names, elem.GetName())
}
return names
}
// pushPluginToTop pushes a plugin to the top of the list.
func (p *PipelineConfig) pushPluginToTop(elem *PipelineElement) {
newSlc := []*PipelineElement{elem}
newSlc = append(newSlc, p.Elements...)
p.Elements = newSlc
}
// PipelineElement represents an `GstElement` in a `GstPipeline` when building a Pipeline with `NewPipelineFromConfig`.
// The Name should coorespond to a valid gstreamer plugin name. The data are additional
// fields to set on the element. If SinkCaps is non-nil, they are applied to the sink of this
// element.
type PipelineElement struct {
Name string
SinkCaps Caps
Data map[string]interface{}
}
// GetName returns the name to use when creating Elements from this configuration.
func (p *PipelineElement) GetName() string { return p.Name }
// NewPipelineFromConfig builds a new pipeline from the given PipelineConfig. The plugins provided
// in the configuration will be linked in the order they are given.
// If using PipelineWrite, you can optionally pass a Caps object to filter between the write-buffer
// and the start of the pipeline.
func NewPipelineFromConfig(cfg *PipelineConfig, flags PipelineFlags, caps Caps) (pipeline *Pipeline, err error) {
// create a new empty pipeline instance
pipeline, err = NewPipeline(flags)
if err != nil {
return nil, err
}
// if any error happens while setting up the pipeline, immediately free it
defer func() {
if err != nil {
if cerr := pipeline.Close(); cerr != nil {
fmt.Println("Failed to close pipeline:", err)
}
}
}()
if cfg.Elements == nil {
cfg.Elements = make([]*PipelineElement, 0)
}
if flags.has(PipelineWrite) {
if flags.has(PipelineUseGstApp) {
cfg.pushPluginToTop(&PipelineElement{
Name: "appsrc",
Data: map[string]interface{}{
"block": true, // TODO: make these all configurable
"emit-signals": false, // https://gstreamer.freedesktop.org/documentation/app/appsrc.html?gi-language=c
"is-live": true,
"max-bytes": 200000,
// "size": 0, // If this is known we should specify it
},
SinkCaps: caps,
})
} else {
cfg.pushPluginToTop(&PipelineElement{
Name: "fdsrc",
Data: map[string]interface{}{
"fd": pipeline.writerFd(),
},
SinkCaps: caps,
})
}
}
if flags.has(PipelineRead) {
if flags.has(PipelineUseGstApp) {
cfg.Elements = append(cfg.Elements, &PipelineElement{
Name: "appsink",
Data: map[string]interface{}{
"emit-signals": false,
},
})
} else {
cfg.Elements = append(cfg.Elements, &PipelineElement{
Name: "fdsink",
Data: map[string]interface{}{
"fd": pipeline.readerFd(),
},
})
}
}
// retrieve a list of the plugin names
pluginNames := cfg.ElementNames()
// build all the elements
var elements map[int]*Element
elements, err = NewElementMany(pluginNames...)
if err != nil {
return
}
// iterate the plugin names and add them to the pipeline
for idx, name := range pluginNames {
// get the current plugin and element
currentPlugin := cfg.GetElementByName(name)
currentElem := elements[idx]
// Iterate any data with the plugin and set it on the element
for key, value := range currentPlugin.Data {
if err = currentElem.Set(key, value); err != nil {
return
}
}
// Add the element to the pipeline
if err = pipeline.Add(currentElem); err != nil {
return
}
// If this is the first element continue
if idx == 0 {
continue
}
// get the last element in the chain
lastPluginName := pluginNames[idx-1]
lastElem := elements[idx-1]
lastPlugin := cfg.GetElementByName(lastPluginName)
if lastPlugin == nil {
// this should never happen, since only used internally,
// but safety from panic
continue
}
// If this is the second element and we are configuring writing
// call link on the last element
if idx == 1 && flags.has(PipelineWrite) {
pipeline.LinkWriterTo(lastElem)
if flags.has(PipelineUseGstApp) {
pipeline.appSrc = wrapAppSrc(lastElem)
}
}
// If this is the last element and we are configuring reading
// call link on the element
if idx == len(pluginNames)-1 && flags.has(PipelineRead) {
pipeline.LinkReaderTo(currentElem)
if flags.has(PipelineUseGstApp) {
pipeline.appSink = wrapAppSink(currentElem)
}
}
// If there are sink caps on the last element, do a filtered link to this one and continue
if lastPlugin.SinkCaps != nil {
if err = lastElem.LinkFiltered(currentElem, lastPlugin.SinkCaps); err != nil {
return
}
continue
}
// link the last element to this element
if err = lastElem.Link(currentElem); err != nil {
return
}
}
pipeline.pipelineFromHelper = true
return
}

142
gst/gst_pipeline_string.go Normal file
View File

@@ -0,0 +1,142 @@
package gst
import (
"errors"
"fmt"
"strings"
)
// NewPipelineFromLaunchString returns a new GstPipeline from the given launch string. If flags
// contain PipelineRead or PipelineWrite, the launch string is further formatted accordingly.
//
// If using PipelineWrite, you should generally start your pipeline with the caps of the source.
func NewPipelineFromLaunchString(launchStr string, flags PipelineFlags) (*Pipeline, error) {
// reformat the string to point at the writerFd
if flags.has(PipelineWrite) {
if flags.has(PipelineUseGstApp) {
if launchStr == "" {
launchStr = "appsrc"
} else {
launchStr = fmt.Sprintf("appsrc ! %s", launchStr)
}
} else {
if launchStr == "" {
launchStr = "fdsrc"
} else {
launchStr = fmt.Sprintf("fdsrc ! %s", launchStr)
}
}
}
if flags.has(PipelineRead) {
if flags.has(PipelineUseGstApp) {
if launchStr == "" {
launchStr = "appsink emit-signals=false"
} else {
launchStr = fmt.Sprintf("%s ! appsink emit-signals=false", launchStr)
}
} else {
if launchStr == "" {
launchStr = "fdsink"
} else {
launchStr = fmt.Sprintf("%s ! fdsink", launchStr)
}
}
}
pipelineElement, err := newPipelineFromString(launchStr)
if err != nil {
return nil, err
}
pipeline := wrapPipeline(pipelineElement)
if err := applyFlags(pipeline, flags); err != nil {
return nil, err
}
if flags.has(PipelineWrite) {
sources, err := pipeline.GetSourceElements()
if err != nil {
return nil, err
}
var srcType string
if flags.has(PipelineUseGstApp) {
srcType = "appsrc"
} else {
srcType = "fdsrc"
}
var pipelineSrc *Element
for _, src := range sources {
if strings.Contains(src.Name(), srcType) {
pipelineSrc = src
} else {
src.Unref()
}
}
if pipelineSrc == nil {
return nil, errors.New("Could not detect pipeline source")
}
defer pipelineSrc.Unref()
if flags.has(PipelineUseGstApp) {
pipeline.appSrc = wrapAppSrc(pipelineSrc)
} else {
if err := pipelineSrc.Set("fd", pipeline.writerFd()); err != nil {
return nil, err
}
}
}
if flags.has(PipelineRead) {
sinks, err := pipeline.GetSinkElements()
if err != nil {
return nil, err
}
var sinkType string
if flags.has(PipelineUseGstApp) {
sinkType = "appsink"
} else {
sinkType = "fdsink"
}
var pipelineSink *Element
for _, sink := range sinks {
if strings.Contains(sink.Name(), sinkType) {
pipelineSink = sink
} else {
sink.Unref()
}
}
if pipelineSink == nil {
return nil, errors.New("Could not detect pipeline sink")
}
defer pipelineSink.Unref()
if flags.has(PipelineUseGstApp) {
pipeline.appSink = wrapAppSink(pipelineSink)
} else {
if err := pipelineSink.Set("fd", pipeline.readerFd()); err != nil {
return nil, err
}
}
}
// signal that this pipeline was made from a string and therefore already linked
pipeline.pipelineFromHelper = true
return pipeline, err
}

114
gst/gst_plugin.go Normal file
View File

@@ -0,0 +1,114 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"unsafe"
)
// PluginFeature is a go representation of a GstPluginFeature
type PluginFeature struct{ *Object }
// Instance returns the underlying GstPluginFeature instance
func (p *PluginFeature) Instance() *C.GstPluginFeature { return C.toGstPluginFeature(p.unsafe()) }
func wrapPluginFeature(p *C.GstPluginFeature) *PluginFeature {
return &PluginFeature{wrapObject(C.toGstObject(unsafe.Pointer(p)))}
}
// GetPlugin returns the plugin that provides this feature or nil. Unref after usage.
func (p *PluginFeature) GetPlugin() *Plugin {
plugin := C.gst_plugin_feature_get_plugin((*C.GstPluginFeature)(p.Instance()))
if plugin == nil {
return nil
}
return wrapPlugin(plugin)
}
// GetPluginName returns the name of the plugin that provides this feature.
func (p *PluginFeature) GetPluginName() string {
pluginName := C.gst_plugin_feature_get_plugin_name((*C.GstPluginFeature)(p.Instance()))
if pluginName == nil {
return ""
}
return C.GoString(pluginName)
}
// Plugin is a go representation of a GstPlugin.
type Plugin struct{ *Object }
// Instance returns the underlying GstPlugin instance.
func (p *Plugin) Instance() *C.GstPlugin { return C.toGstPlugin(p.unsafe()) }
func wrapPlugin(p *C.GstPlugin) *Plugin {
return &Plugin{wrapObject(C.toGstObject(unsafe.Pointer(p)))}
}
// Description returns the description for this plugin.
func (p *Plugin) Description() string {
ret := C.gst_plugin_get_description((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// Filename returns the filename for this plugin.
func (p *Plugin) Filename() string {
ret := C.gst_plugin_get_filename((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// Version returns the version for this plugin.
func (p *Plugin) Version() string {
ret := C.gst_plugin_get_version((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// License returns the license for this plugin.
func (p *Plugin) License() string {
ret := C.gst_plugin_get_license((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// Source returns the source module for this plugin.
func (p *Plugin) Source() string {
ret := C.gst_plugin_get_source((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// Package returns the binary package for this plugin.
func (p *Plugin) Package() string {
ret := C.gst_plugin_get_package((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}
// Origin returns the origin URL for this plugin.
func (p *Plugin) Origin() string {
ret := C.gst_plugin_get_origin((*C.GstPlugin)(p.Instance()))
if ret == nil {
return ""
}
return C.GoString(ret)
}

53
gst/gst_registry.go Normal file
View File

@@ -0,0 +1,53 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
// Registry is a go representation of a GstRegistry.
type Registry struct{ *Object }
// GetRegistry returns the default global GstRegistry.
func GetRegistry() *Registry {
registry := C.gst_registry_get()
return wrapRegistry(registry)
}
// Instance returns the underlying GstRegistry instance.
func (r *Registry) Instance() *C.GstRegistry {
return C.toGstRegistry(r.unsafe())
}
// FindPlugin retrieves the plugin by the given name. Unref after usage.
func (r *Registry) FindPlugin(name string) (*Plugin, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
plugin := C.gst_registry_find_plugin((*C.GstRegistry)(r.Instance()), (*C.gchar)(cName))
if plugin == nil {
return nil, fmt.Errorf("No plugin named %s found", name)
}
return wrapPlugin(plugin), nil
}
// LookupFeature looks up the given plugin feature by name. Unref after usage.
func (r *Registry) LookupFeature(name string) (*PluginFeature, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
feat := C.gst_registry_lookup_feature((*C.GstRegistry)(r.Instance()), (*C.gchar)(cName))
if feat == nil {
return nil, fmt.Errorf("No feature named %s found", name)
}
return wrapPluginFeature(feat), nil
}
func wrapRegistry(reg *C.GstRegistry) *Registry {
return &Registry{wrapObject(C.toGstObject(unsafe.Pointer(reg)))}
}

31
gst/gst_uri.go Normal file
View File

@@ -0,0 +1,31 @@
package gst
/*
#cgo pkg-config: gstreamer-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
#include <gst/gst.h>
#include "gst.go.h"
*/
import "C"
// URIType casts C GstURIType to a go type
type URIType C.GstURIType
// Type cast URI types
const (
URIUnknown URIType = C.GST_URI_UNKNOWN // (0) The URI direction is unknown
URISink = C.GST_URI_SINK // (1) The URI is a consumer.
URISource = C.GST_URI_SRC // (2) - The URI is a producer.
)
func (u URIType) String() string {
switch u {
case URIUnknown:
return "Unknown"
case URISink:
return "Sink"
case URISource:
return "Source"
}
return ""
}