mirror of
https://github.com/go-gst/go-gst.git
synced 2025-10-04 23:52:55 +08:00
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tinyzimmer/go-gst/examples"
|
|
"github.com/tinyzimmer/go-gst/gst"
|
|
)
|
|
|
|
var srcFile string
|
|
|
|
func buildPipeline() (*gst.Pipeline, error) {
|
|
gst.Init(nil)
|
|
|
|
pipeline, err := gst.NewPipeline("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
src, err := gst.NewElement("filesrc")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decodebin, err := gst.NewElement("decodebin")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
src.Set("location", srcFile)
|
|
|
|
pipeline.AddMany(src, decodebin)
|
|
src.Link(decodebin)
|
|
|
|
// Connect to decodebin's pad-added signal, that is emitted whenever
|
|
// it found another stream from the input file and found a way to decode it to its raw format.
|
|
// decodebin automatically adds a src-pad for this raw stream, which
|
|
// we can use to build the follow-up pipeline.
|
|
decodebin.Connect("pad-added", func(self *gst.Element, srcPad *gst.Pad) {
|
|
|
|
// Try to detect whether this is video or audio
|
|
var isAudio, isVideo bool
|
|
caps := srcPad.GetCurrentCaps()
|
|
for i := 0; i < caps.GetSize(); i++ {
|
|
st := caps.GetStructureAt(i)
|
|
if strings.HasPrefix(st.Name(), "audio/") {
|
|
isAudio = true
|
|
}
|
|
if strings.HasPrefix(st.Name(), "video/") {
|
|
isVideo = true
|
|
}
|
|
}
|
|
|
|
fmt.Printf("New pad added, is_audio=%v, is_video=%v\n", isAudio, isVideo)
|
|
|
|
if !isAudio && !isVideo {
|
|
err := errors.New("Could not detect media stream type")
|
|
// We can send errors directly to the pipeline bus if they occur.
|
|
// These will be handled downstream.
|
|
msg := gst.NewErrorMessage(self, gst.NewGError(1, err), fmt.Sprintf("Received caps: %s", caps.String()), nil)
|
|
pipeline.GetPipelineBus().Post(msg)
|
|
return
|
|
}
|
|
|
|
if isAudio {
|
|
// decodebin found a raw audiostream, so we build the follow-up pipeline to
|
|
// play it on the default audio playback device (using autoaudiosink).
|
|
elements, err := gst.NewElementMany("queue", "audioconvert", "audioresample", "autoaudiosink")
|
|
if err != nil {
|
|
msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "", nil)
|
|
pipeline.GetPipelineBus().Post(msg)
|
|
fmt.Println("ERROR: Could not create elements for audio pipeline")
|
|
return
|
|
}
|
|
pipeline.AddMany(elements...)
|
|
gst.ElementLinkMany(elements...)
|
|
|
|
// !!ATTENTION!!:
|
|
// This is quite important and people forget it often. Without making sure that
|
|
// the new elements have the same state as the pipeline, things will fail later.
|
|
// They would still be in Null state and can't process data.
|
|
for _, e := range elements {
|
|
e.SyncStateWithParent()
|
|
}
|
|
|
|
// The queue was the first element returned above
|
|
queue := elements[0]
|
|
// Get the queue element's sink pad and link the decodebin's newly created
|
|
// src pad for the audio stream to it.
|
|
sinkPad := queue.GetStaticPad("sink")
|
|
srcPad.Link(sinkPad)
|
|
|
|
} else if isVideo {
|
|
// decodebin found a raw videostream, so we build the follow-up pipeline to
|
|
// display it using the autovideosink.
|
|
elements, err := gst.NewElementMany("queue", "videoconvert", "videoscale", "autovideosink")
|
|
if err != nil {
|
|
msg := gst.NewErrorMessage(self, gst.NewGError(2, err), "", nil)
|
|
pipeline.GetPipelineBus().Post(msg)
|
|
fmt.Println("ERROR: Could not create elements for audio pipeline")
|
|
return
|
|
}
|
|
pipeline.AddMany(elements...)
|
|
gst.ElementLinkMany(elements...)
|
|
|
|
for _, e := range elements {
|
|
e.SyncStateWithParent()
|
|
}
|
|
|
|
queue := elements[0]
|
|
// Get the queue element's sink pad and link the decodebin's newly created
|
|
// src pad for the video stream to it.
|
|
sinkPad := queue.GetStaticPad("sink")
|
|
srcPad.Link(sinkPad)
|
|
}
|
|
})
|
|
return pipeline, nil
|
|
}
|
|
|
|
func handleMessage(msg *gst.Message) error {
|
|
defer msg.Unref() // Messages are a good candidate for trying out runtime finalizers
|
|
|
|
switch msg.Type() {
|
|
case gst.MessageEOS:
|
|
return errors.New("end-of-stream")
|
|
case gst.MessageError:
|
|
return msg.ParseError()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runPipeline(pipeline *gst.Pipeline) error {
|
|
pipeline.SetState(gst.StatePlaying)
|
|
|
|
bus := pipeline.GetPipelineBus()
|
|
|
|
for {
|
|
msg := bus.TimedPop(time.Duration(-1))
|
|
if msg == nil {
|
|
break
|
|
}
|
|
if err := handleMessage(msg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&srcFile, "f", "", "The file to decode")
|
|
flag.Parse()
|
|
if srcFile == "" {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
examples.RunLoop(func(loop *gst.MainLoop) error {
|
|
pipeline, err := buildPipeline()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return runPipeline(pipeline)
|
|
})
|
|
}
|