mirror of
https://github.com/go-gst/go-gst.git
synced 2025-10-06 00:17:00 +08:00
initial commit
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal 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
7
Makefile
Normal 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)
|
71
README.md
71
README.md
@@ -1,2 +1,71 @@
|
|||||||
# go-gst
|
# go-gst
|
||||||
Gstreamer bindings and utilities for golang
|
|
||||||
|
Go bindings for the gstreamer C library
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/tinyzimmer/go-gst/gst)
|
||||||
|
[](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
66
cmd/go-gst/colors.go
Normal 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
62
cmd/go-gst/completion.go
Normal 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
144
cmd/go-gst/gif.go
Normal 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
325
cmd/go-gst/inspect.go
Normal 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
73
cmd/go-gst/launch.go
Normal 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
14
cmd/go-gst/log.go
Normal 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
47
cmd/go-gst/main.go
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
98
cmd/go-gst/print_object_properties.go
Normal file
98
cmd/go-gst/print_object_properties.go
Normal 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
98
cmd/go-gst/util.go
Normal 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
308
cmd/go-gst/websocket.go
Normal 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
9
go.mod
Normal 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
130
go.sum
Normal 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
97
gst/c_util.go
Normal 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
141
gst/doc.go
Normal 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
506
gst/gst.go.h
Normal 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
100
gst/gst_app_sink.go
Normal 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
84
gst/gst_app_src.go
Normal 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
72
gst/gst_bin.go
Normal 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
78
gst/gst_bus.go
Normal 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
106
gst/gst_caps.go
Normal 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
22
gst/gst_clock.go
Normal 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
192
gst/gst_element.go
Normal 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
101
gst/gst_element_factory.go
Normal 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
156
gst/gst_message.go
Normal 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
192
gst/gst_object.go
Normal 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
106
gst/gst_pad.go
Normal 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
528
gst/gst_pipeline.go
Normal 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
194
gst/gst_pipeline_config.go
Normal 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
142
gst/gst_pipeline_string.go
Normal 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
114
gst/gst_plugin.go
Normal 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
53
gst/gst_registry.go
Normal 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
31
gst/gst_uri.go
Normal 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 ""
|
||||||
|
}
|
Reference in New Issue
Block a user