@@ -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