From 704f3471c378c35c4eb89145e4bc2d40d3768e79 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 23 Jan 2019 15:22:51 +0300 Subject: [PATCH] Images to video example --- codecCtx.go | 4 +- examples/images-to-video.go | 198 ++++++++++++++++++ .../{video-to-jpeg.go => video-to-png.go} | 12 +- utils.go | 4 - 4 files changed, 208 insertions(+), 10 deletions(-) create mode 100644 examples/images-to-video.go rename examples/{video-to-jpeg.go => video-to-png.go} (93%) diff --git a/codecCtx.go b/codecCtx.go index 0971725..c652b3a 100644 --- a/codecCtx.go +++ b/codecCtx.go @@ -569,13 +569,13 @@ func (cc *CodecCtx) Encode(frames []*Frame, drain int) ([]*Packet, error) { ret = int(C.avcodec_send_frame(cc.avCodecCtx, frame.avFrame)) } if ret < 0 { - return nil, fmt.Errorf("error during encoding - %s", AvError(ret)) + return nil, AvError(ret) } for { pkt := NewPacket() ret = int(C.avcodec_receive_packet(cc.avCodecCtx, &pkt.avPacket)) - if ret != 0 { + if ret < 0 { pkt.Free() break } diff --git a/examples/images-to-video.go b/examples/images-to-video.go new file mode 100644 index 0000000..f8ee72e --- /dev/null +++ b/examples/images-to-video.go @@ -0,0 +1,198 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "path/filepath" + + "github.com/3d0c/gmf" +) + +var pts int64 = 0 + +func initOst(name string, oc *gmf.FmtCtx, ist *gmf.Stream) (*gmf.Stream, error) { + var ( + cc *gmf.CodecCtx + ost *gmf.Stream + options []gmf.Option + ) + + codec, err := gmf.FindEncoder(name) + if err != nil { + return nil, err + } + + if ost = oc.NewStream(codec); ost == nil { + return nil, fmt.Errorf("unable to create new stream in output context") + } + + if cc = gmf.NewCodecCtx(codec); cc == nil { + return nil, fmt.Errorf("unable to create codec context") + } + defer gmf.Release(cc) + + if oc.IsGlobalHeader() { + cc.SetFlag(gmf.CODEC_FLAG_GLOBAL_HEADER) + } + + if codec.IsExperimental() { + cc.SetStrictCompliance(gmf.FF_COMPLIANCE_EXPERIMENTAL) + } + + options = append( + []gmf.Option{ + {Key: "time_base", Val: gmf.AVR{Num: 1, Den: 25}}, + }, + ) + + ost.SetTimeBase(gmf.AVR{Num: 1, Den: 25}) + ost.SetRFrameRate(gmf.AVR{Num: 25, Den: 1}) + + options = append( + []gmf.Option{ + {Key: "pixel_format", Val: gmf.AV_PIX_FMT_YUV420P}, + {Key: "video_size", Val: ist.CodecCtx().GetVideoSize()}, + }, + options..., + ) + + cc.SetProfile(gmf.FF_PROFILE_H264_MAIN) + + cc.SetOptions(options) + + if err := cc.Open(nil); err != nil { + return nil, err + } + + ost.SetCodecCtx(cc) + + return ost, nil +} + +func main() { + var ( + src string + dst string + ost *gmf.Stream + pkt *gmf.Packet + frame, dstFrame *gmf.Frame + swsCtx *gmf.SwsCtx + ret int + sources []string = make([]string, 0) + ) + + flag.StringVar(&src, "src", "./tmp", "source images folder") + flag.StringVar(&dst, "dst", "result.mp4", "destination file") + + flag.Parse() + + fis, err := ioutil.ReadDir(src) + if err != nil { + log.Fatalf("Error reding '%s' - %s\n", src, err) + } + + for _, fi := range fis { + ext := filepath.Ext(fi.Name()) + if ext != ".jpg" && ext != ".jpeg" && ext != ".png" { + log.Printf("skipping %s, ext: '%s'\n", fi.Name(), ext) + continue + } + + sources = append(sources, filepath.Join(src, fi.Name())) + } + + if len(sources) == 0 { + log.Fatalf("Not enough source files\n") + } + + octx, err := gmf.NewOutputCtx(dst) + if err != nil { + log.Fatalf("Error creating output context - %s\n", err) + } + defer octx.Free() + + for _, source := range sources { + log.Printf("Loading %s\n", source) + + ictx, err := gmf.NewInputCtx(source) + if err != nil { + log.Fatalf("Error creating input context - %s\n", err) + } + + ist, err := ictx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO) + if err != nil { + log.Fatalf("Error getting source stream - %s\n", err) + } + + if ost == nil { + if ost, err = initOst("libx264", octx, ist); err != nil { + log.Fatalf("Error init output stream - %s\n", err) + } + if err = octx.WriteHeader(); err != nil { + log.Fatalf("%s\n", err) + } + } + + if swsCtx == nil { + swsCtx = gmf.NewSwsCtx(ist.CodecCtx(), ost.CodecCtx(), gmf.SWS_FAST_BILINEAR) + } + + if pkt, err = ictx.GetNextPacket(); err != nil { + log.Fatalf("Error getting packet - %s", err) + } + + frame, ret = ist.CodecCtx().Decode2(pkt) + if ret < 0 { + log.Fatalf("Unexpected error - %s\n", gmf.AvError(ret)) + } + + if dstFrame, err = swsCtx.Scale2(frame); err != nil { + log.Fatalf("Error scaling - %s\n", err) + } + + encode(octx, ost, dstFrame, -1) + + pkt.Free() + ictx.Free() + frame.Free() + } + + encode(octx, ost, nil, 1) + + octx.WriteTrailer() +} + +func encode(octx *gmf.FmtCtx, ost *gmf.Stream, frame *gmf.Frame, drain int) { + if frame != nil { + frame.SetPts(pts) + } + + pts += 1 + + packets, err := ost.CodecCtx().Encode([]*gmf.Frame{frame}, drain) + if err != nil { + log.Fatalf("Error encoding - %s\n", err) + } + if len(packets) == 0 { + return + } + + for _, packet := range packets { + packet.SetPts(gmf.RescaleQ(packet.Pts(), ost.CodecCtx().TimeBase(), ost.TimeBase())) + packet.SetDts(gmf.RescaleQ(packet.Dts(), ost.CodecCtx().TimeBase(), ost.TimeBase())) + + if err = octx.WritePacket(packet); err != nil { + log.Fatalf("Error writing packet - %s\n", err) + } + + packet.Free() + + if drain > 0 { + pts += 1 + } + } + + return +} diff --git a/examples/video-to-jpeg.go b/examples/video-to-png.go similarity index 93% rename from examples/video-to-jpeg.go rename to examples/video-to-png.go index 1bd8805..d77a3f9 100644 --- a/examples/video-to-jpeg.go +++ b/examples/video-to-png.go @@ -1,10 +1,10 @@ package main import ( + "fmt" "log" "os" "runtime/debug" - "strconv" . "github.com/3d0c/gmf" ) @@ -22,10 +22,10 @@ func assert(i interface{}, err error) interface{} { return i } -var i int = 0 +var i, j int = 0, 0 func writeFile(b []byte) { - name := "./tmp/" + strconv.Itoa(i) + ".jpg" + name := fmt.Sprintf("./tmp/%d%d.png", j, i) fp, err := os.Create(name) if err != nil { @@ -37,6 +37,10 @@ func writeFile(b []byte) { fatal(err) } i++ + if i == 9 { + i = 0 + j++ + } }() if n, err := fp.Write(b); err != nil { @@ -63,7 +67,7 @@ func main() { log.Println("No video stream found in", srcFileName) } - codec, err := FindEncoder(AV_CODEC_ID_JPEG2000) + codec, err := FindEncoder(AV_CODEC_ID_PNG) if err != nil { fatal(err) } diff --git a/utils.go b/utils.go index 7bd1a63..e43c43b 100644 --- a/utils.go +++ b/utils.go @@ -109,10 +109,6 @@ func GetSampleFmtName(fmt int32) string { return C.GoString(C.av_get_sample_fmt_name(fmt)) } -// func RescaleRnd(a, b, c int64) int64 { -// return int64(C.av_rescale_rnd(C.int64_t(a), C.int64_t(b), C.int64_t(c), 3)) -// } - func AvInvQ(q AVRational) AVRational { avr := q.AVR() return AVRational{C.int(avr.Den), C.int(avr.Num)}