Skip to content

Commit 6056698

Browse files
committed
WIP working encoding of a train clip to h264 mp4 video using gstreamer
1 parent f2195bb commit 6056698

3 files changed

Lines changed: 156 additions & 1 deletion

File tree

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ require (
5353
github.com/fatih/color v1.18.0 // indirect
5454
github.com/fatih/structtag v1.2.0 // indirect
5555
github.com/felixge/httpsnoop v1.0.4 // indirect
56+
github.com/go-gst/go-glib v1.4.0 // indirect
57+
github.com/go-gst/go-gst v1.4.0 // indirect
5658
github.com/go-logr/logr v1.4.2 // indirect
5759
github.com/go-logr/stdr v1.2.2 // indirect
5860
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@@ -68,6 +70,7 @@ require (
6870
github.com/jmespath/go-jmespath v0.4.0 // indirect
6971
github.com/mattn/go-colorable v0.1.14 // indirect
7072
github.com/mattn/go-isatty v0.0.20 // indirect
73+
github.com/mattn/go-pointer v0.0.1 // indirect
7174
github.com/mattn/go-runewidth v0.0.16 // indirect
7275
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
7376
github.com/mgechev/revive v1.9.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4
6767
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
6868
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
6969
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
70+
github.com/go-gst/go-glib v1.4.0 h1:FB2uVfB0uqz7/M6EaDdWWlBZRQpvFAbWfL7drdw8lAE=
71+
github.com/go-gst/go-glib v1.4.0/go.mod h1:GUIpWmkxQ1/eL+FYSjKpLDyTZx6Vgd9nNXt8dA31d5M=
72+
github.com/go-gst/go-gst v1.4.0 h1:EikB43u4c3wc8d2RzlFRSfIGIXYzDy6Zls2vJqrG2BU=
73+
github.com/go-gst/go-gst v1.4.0/go.mod h1:p8TLGtOxJLcrp6PCkTPdnanwWBxPZvYiHDbuSuwgO3c=
7074
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
7175
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
7276
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -143,6 +147,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
143147
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
144148
github.com/mattn/go-mjpeg v0.0.3 h1:0G/+KddrbI5Hnq83B11O1O4vP7Q6L9MsBu6aW71jhUM=
145149
github.com/mattn/go-mjpeg v0.0.3/go.mod h1:65z7Cj+u5y5K3B8Sy5NtrJFTWAhguGHs9FEkADdx6kE=
150+
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
151+
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
146152
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
147153
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
148154
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=

internal/pkg/stitch/stitch.go

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import (
44
"errors"
55
"fmt"
66
"image"
7+
"image/color"
78
"image/draw"
89
"image/gif"
910
"math"
1011
"time"
1112

13+
"github.com/go-gst/go-glib/glib"
14+
"github.com/go-gst/go-gst/gst"
15+
"github.com/go-gst/go-gst/gst/app"
16+
"github.com/go-gst/go-gst/gst/video"
1217
"github.com/mccutchen/palettor"
1318
"github.com/nfnt/resize"
1419
"github.com/rs/zerolog/log"
@@ -184,6 +189,146 @@ func createGIF(seq sequence, stitched image.Image) (*gif.GIF, error) {
184189
return &g, nil
185190
}
186191

192+
func createH264(seq sequence, stitched image.Image) (*gif.GIF, error) {
193+
// https://github.com/go-gst/go-gst/blob/v1.4.0/examples/appsrc/main.go
194+
195+
// SW: x264enc
196+
// HW on RPi: v4l2h264enc
197+
// HW on PC AMD: va264enc
198+
199+
// appsrc ! x264enc ! mp4mux ! filesync location=/tmp/test.mp4
200+
201+
gst.Init(nil)
202+
203+
pipeline, err := gst.NewPipeline("")
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
encoder := "x264enc"
209+
elems, err := gst.NewElementMany("appsrc", "videoconvert", encoder, "h264parse", "mp4mux" /*"autovideosink"*/, "filesink")
210+
//elems, err := gst.NewElementMany("appsrc", "videoconvert", "autovideosink")
211+
if err != nil {
212+
return nil, err
213+
}
214+
215+
pipeline.AddMany(elems...)
216+
gst.ElementLinkMany(elems...)
217+
218+
src := app.SrcFromElement(elems[0])
219+
elems[5].SetArg("location", "/tmp/test.mp4")
220+
//elems[4].SetArg("sync", "false")
221+
222+
// Specify the format we want to provide as application into the pipeline
223+
// by creating a video info with the given format and creating caps from it for the appsrc element.
224+
videoInfo := video.NewInfo().
225+
WithFormat(video.FormatRGBA, 300, 300). /*uint(seq.frames[0].Bounds().Dx()), uint(seq.frames[0].Bounds().Dy()))*/
226+
WithFPS(gst.Fraction(2, 1)) // FIXME
227+
228+
src.SetCaps(videoInfo.ToCaps())
229+
src.SetProperty("format", gst.FormatTime)
230+
231+
// Initialize a frame counter
232+
var i int
233+
//palette := video.FormatRGB8P.Palette()
234+
235+
// Since our appsrc element operates in pull mode (it asks us to provide data),
236+
// we add a handler for the need-data callback and provide new data from there.
237+
// In our case, we told gstreamer that we do 2 frames per second. While the
238+
// buffers of all elements of the pipeline are still empty, this will be called
239+
// a couple of times until all of them are filled. After this initial period,
240+
// this handler will be called (on average) twice per second.
241+
src.SetCallbacks(&app.SourceCallbacks{
242+
NeedDataFunc: func(self *app.Source, _ uint) {
243+
244+
// If we've reached the end of the palette, end the stream.
245+
if i == len(seq.frames) {
246+
src.EndStream()
247+
return
248+
}
249+
250+
log.Debug().Int("frame", i).Msg("Producing frame")
251+
252+
// Create a buffer that can hold exactly one video RGBA frame.
253+
buffer := gst.NewBufferWithSize(videoInfo.Size())
254+
255+
// For each frame we produce, we set the timestamp when it should be displayed
256+
// The autovideosink will use this information to display the frame at the right time.
257+
buffer.SetPresentationTimestamp(gst.ClockTime(time.Duration(i) * 33 * time.Millisecond)) // FIXME
258+
259+
// Produce an image frame for this iteration.
260+
pixels := seq.frames[i].(*image.RGBA).Pix
261+
//pixels := produceImageFrame(palette[i])
262+
263+
// At this point, buffer is only a reference to an existing memory region somewhere.
264+
// When we want to access its content, we have to map it while requesting the required
265+
// mode of access (read, read/write).
266+
// See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html
267+
//
268+
// There are convenience wrappers for building buffers directly from byte sequences as
269+
// well.
270+
buffer.Map(gst.MapWrite).WriteData(pixels)
271+
buffer.Unmap()
272+
273+
//buffer := gst.NewBufferFromBytes(pixels)
274+
275+
// Push the buffer onto the pipeline.
276+
self.PushBuffer(buffer)
277+
278+
log.Debug().Msg("buffer pushed")
279+
280+
i++
281+
},
282+
})
283+
284+
mainLoop := glib.NewMainLoop(glib.MainContextDefault(), false)
285+
286+
pipeline.Ref()
287+
defer pipeline.Unref()
288+
289+
pipeline.SetState(gst.StatePlaying)
290+
291+
// Retrieve the bus from the pipeline and add a watch function
292+
pipeline.GetPipelineBus().AddWatch(func(msg *gst.Message) bool {
293+
log.Warn().Str("msg", msg.String()).Msg("gstreamer message")
294+
switch msg.Type() {
295+
case gst.MessageEOS:
296+
//time.Sleep(5 * time.Second)
297+
//pipeline.SetState(gst.StateNull)
298+
//time.Sleep(5 * time.Second)
299+
//mainLoop.Quit()
300+
//time.Sleep(5 * time.Second)
301+
//return false
302+
}
303+
//if err := handleMessage(msg); err != nil {
304+
// fmt.Println(err)
305+
// loop.Quit()
306+
// return false
307+
//}
308+
return true
309+
})
310+
311+
mainLoop.Run()
312+
//mainLoop.RunError()
313+
314+
return nil, errors.New("look at /tmp/test.mp4 :)")
315+
}
316+
func produceImageFrame(c color.Color) []uint8 {
317+
width := 300
318+
height := 300
319+
upLeft := image.Point{0, 0}
320+
lowRight := image.Point{width, height}
321+
img := image.NewRGBA(image.Rectangle{upLeft, lowRight})
322+
323+
for x := 0; x < width; x++ {
324+
for y := 0; y < height; y++ {
325+
img.Set(x, y, c)
326+
}
327+
}
328+
329+
return img.Pix
330+
}
331+
187332
// fitAndStitch tries to stitch an image from a sequence.
188333
// Will first try to fit a constant acceleration speed model for smoothing.
189334
// Might modify seq (drops leading frames with no movement).
@@ -241,7 +386,8 @@ func fitAndStitch(seq sequence, c Config) (*Train, error) {
241386
return nil, fmt.Errorf("unable to assemble image: %w", err)
242387
}
243388

244-
gif, err := createGIF(seq, img)
389+
//gif, err := createGIF(seq, img)
390+
gif, err := createH264(seq, img)
245391
if err != nil {
246392
panic(err)
247393
}

0 commit comments

Comments
 (0)