From 7619cab84db07e1bbf092c09649e75a4a5ed0db9 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Sun, 21 Jan 2024 11:45:18 +0100 Subject: [PATCH 01/15] Draft implementation for SWS scale --- examples/scaling/main.go | 164 +++++++++++++++++++++++++++++++++++++++ sws_context.go | 82 ++++++++++++++++++++ sws_context_test.go | 69 ++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 examples/scaling/main.go create mode 100644 sws_context.go create mode 100644 sws_context_test.go diff --git a/examples/scaling/main.go b/examples/scaling/main.go new file mode 100644 index 0000000..4bb62e2 --- /dev/null +++ b/examples/scaling/main.go @@ -0,0 +1,164 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "strings" + + "github.com/asticode/go-astiav" +) + +var ( + input = flag.String("i", "", "the input path") +) + +type stream struct { + decCodec *astiav.Codec + decCodecContext *astiav.CodecContext + inputStream *astiav.Stream +} + +func main() { + // Handle ffmpeg logs + astiav.SetLogLevel(astiav.LogLevelDebug) + astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) { + log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l) + }) + + // Parse flags + flag.Parse() + + // Usage + if *input == "" { + log.Println("Usage: -i ") + return + } + + // Alloc packet + pkt := astiav.AllocPacket() + defer pkt.Free() + + // Alloc frame + f := astiav.AllocFrame() + defer f.Free() + + // Alloc input format context + inputFormatContext := astiav.AllocFormatContext() + if inputFormatContext == nil { + log.Fatal(errors.New("main: input format context is nil")) + } + defer inputFormatContext.Free() + + // Open input + if err := inputFormatContext.OpenInput(*input, nil, nil); err != nil { + log.Fatal(fmt.Errorf("main: opening input failed: %w", err)) + } + defer inputFormatContext.CloseInput() + + // Find stream info + if err := inputFormatContext.FindStreamInfo(nil); err != nil { + log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err)) + } + + // Loop through streams + streams := make(map[int]*stream) // Indexed by input stream index + for _, is := range inputFormatContext.Streams() { + // Only process audio or video + if is.CodecParameters().MediaType() != astiav.MediaTypeVideo { + continue + } + + // Create stream + s := &stream{inputStream: is} + + // Find decoder + if s.decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()); s.decCodec == nil { + log.Fatal(errors.New("main: codec is nil")) + } + + // Alloc codec context + if s.decCodecContext = astiav.AllocCodecContext(s.decCodec); s.decCodecContext == nil { + log.Fatal(errors.New("main: codec context is nil")) + } + defer s.decCodecContext.Free() + + // Update codec context + if err := is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil { + log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err)) + } + + // Open codec context + if err := s.decCodecContext.Open(s.decCodec, nil); err != nil { + log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err)) + } + + // Add stream + streams[is.Index()] = s + } + + sws_created := false + reso_changed := false + var sws *astiav.SWSContext + + dstFrame := astiav.AllocFrame() + defer dstFrame.Free() + + // Loop through packets + for { + // Read frame + if err := inputFormatContext.ReadFrame(pkt); err != nil { + if errors.Is(err, astiav.ErrEof) { + break + } + log.Fatal(fmt.Errorf("main: reading frame failed: %w", err)) + } + + // Get stream + s, ok := streams[pkt.StreamIndex()] + if !ok { + continue + } + + // Send packet + if err := s.decCodecContext.SendPacket(pkt); err != nil { + log.Fatal(fmt.Errorf("main: sending packet failed: %w", err)) + } + + // Loop + for { + // Receive frame + if err := s.decCodecContext.ReceiveFrame(f); err != nil { + if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { + break + } + log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err)) + } + + if !sws_created { + sws = astiav.CreateSwsContext(f.Width(), f.Height(), f.PixelFormat(), 480, 270, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) + sws_created = true + } + + err := sws.Scale(f, dstFrame) + if err != nil { + log.Println("Scaling fails") + } else { + // Do something with decoded frame + log.Printf("orig frame: %dx%d %s", f.Width(), f.Height(), f.PixelFormat().String()) + log.Printf("scaled frame: %dx%d %s", dstFrame.Width(), dstFrame.Height(), dstFrame.PixelFormat().String()) + + // Change reso after first scaled frame just for demo + if !reso_changed { + sws = sws.ChangeResolution(100, 100) + reso_changed = true + } + } + + } + } + + // Success + log.Println("success") +} diff --git a/sws_context.go b/sws_context.go new file mode 100644 index 0000000..7f47575 --- /dev/null +++ b/sws_context.go @@ -0,0 +1,82 @@ +package astiav + +//#cgo pkg-config: libswscale +//#include +import "C" +import ( + "fmt" +) + +// https://github.com/FFmpeg/FFmpeg/blob/n4.2.7/libswscale/swscale_internal.h#L280 +type SWSContext struct { + c *C.struct_SwsContext + dstFormat PixelFormat + srcFormat PixelFormat + srcW int + srcH int + dstW int + dstH int + flags int + dstFrame *Frame +} + +const ( + SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR + SWS_BILINEAR = C.SWS_BILINEAR + SWS_BICUBIC = C.SWS_BICUBIC + SWS_X = C.SWS_X + SWS_POINT = C.SWS_POINT + SWS_AREA = C.SWS_AREA + SWS_BICUBLIN = C.SWS_BICUBLIN + SWS_GAUSS = C.SWS_GAUSS + SWS_SINC = C.SWS_SINC + SWS_LANCZOS = C.SWS_LANCZOS + SWS_SPLINE = C.SWS_SPLINE +) + +func CreateSwsContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags int, dstFrame *Frame) *SWSContext { + dstFrame.SetPixelFormat(dstFormat) + dstFrame.SetWidth(dstW) + dstFrame.SetHeight(dstH) + dstFrame.AllocBuffer(1) + + swsCtx := C.sws_getContext( + C.int(srcW), + C.int(srcH), + C.enum_AVPixelFormat(srcFormat), + C.int(dstW), + C.int(dstH), + C.enum_AVPixelFormat(dstFormat), + C.int(flags), + nil, nil, nil, + ) + if swsCtx == nil { + return nil + } + return &SWSContext{c: swsCtx, dstFormat: dstFormat, srcFormat: srcFormat, srcW: srcW, srcH: srcH, dstW: dstW, dstH: dstH, flags: flags, dstFrame: dstFrame} +} + +func (sc *SWSContext) ChangeResolution(dstW, dstH int) *SWSContext { + sc.Free() + return CreateSwsContext(sc.srcW, sc.srcH, sc.srcFormat, dstW, dstH, sc.dstFormat, sc.flags, sc.dstFrame) +} + +func (sc *SWSContext) Scale(srcFrame, dstFrame *Frame) error { + height := int( + C.sws_scale( + sc.c, + &srcFrame.c.data[0], + &srcFrame.c.linesize[0], + 0, + C.int(srcFrame.Height()), + &dstFrame.c.data[0], &dstFrame.c.linesize[0])) + + if height != dstFrame.Height() { + return fmt.Errorf("sws_scale did not process all lines, expected: %d, got: %d", dstFrame.Height(), height) + } + return nil +} + +func (sc *SWSContext) Free() { + C.sws_freeContext(sc.c) +} diff --git a/sws_context_test.go b/sws_context_test.go new file mode 100644 index 0000000..52ccd8a --- /dev/null +++ b/sws_context_test.go @@ -0,0 +1,69 @@ +package astiav_test + +import ( + "image" + "reflect" + "testing" + + "github.com/asticode/go-astiav" + "github.com/stretchr/testify/require" +) + +// Test constants for source and destination dimensions and formats +const ( + srcW = 100 + srcH = 100 + dstW = 200 + dstH = 200 + secondDstW = 300 + secondDstH = 300 + srcFormat = astiav.PixelFormatYuv420P + dstFormat = astiav.PixelFormatRgba +) + +// assertImageType is a helper function to check the type of an image. +func assertImageType(t *testing.T, img image.Image, expectedType reflect.Type) { + actualType := reflect.TypeOf(img) + require.Equal(t, expectedType, actualType, "Image type does not match") +} + +// TestSWS tests the scaling functionality provided by the SWSContext. +func TestSWS(t *testing.T) { + // Allocate and initialize source and destination frames + srcFrame := astiav.AllocFrame() + defer srcFrame.Free() + dstFrame := astiav.AllocFrame() + defer dstFrame.Free() + + srcFrame.SetHeight(srcH) + srcFrame.SetWidth(srcW) + srcFrame.SetPixelFormat(srcFormat) + srcFrame.AllocBuffer(1) + srcFrame.ImageFillBlack() // Fill the source frame with black for testing + + // Create SWSContext for scaling and verify it's not nil + swsc := astiav.CreateSwsContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, astiav.SWS_BILINEAR, dstFrame) + require.NotNil(t, swsc) + + // Perform scaling and verify no errors + err := swsc.Scale(srcFrame, dstFrame) + require.NoError(t, err) + + // Change resolution and perform scaling again + swsc = swsc.ChangeResolution(secondDstW, secondDstH) + err2 := swsc.Scale(srcFrame, dstFrame) + require.NoError(t, err2) + + // Verify the dimensions and format of the destination frame + require.Equal(t, secondDstH, dstFrame.Height()) + require.Equal(t, secondDstW, dstFrame.Width()) + require.Equal(t, dstFormat, dstFrame.PixelFormat()) + + // Convert frame data to image and perform additional verifications + i1, err := dstFrame.Data().Image() + require.NoError(t, err) + require.Equal(t, secondDstW, i1.Bounds().Dx()) + require.Equal(t, secondDstH, i1.Bounds().Dy()) + assertImageType(t, i1, reflect.TypeOf((*image.NRGBA)(nil))) + swsc.Free() +} From 760fb8c4273010b2d5edcdd19e019358603999fb Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Sun, 21 Jan 2024 18:48:03 +0100 Subject: [PATCH 02/15] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 15bcd7c..ac891b9 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/asticode/go-astiav +module github.com/Cacsjep/go-astiav go 1.17 From 7bc348e7ff2c842a22434c0575d4fcb28a5d6d08 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Sun, 21 Jan 2024 18:48:20 +0100 Subject: [PATCH 03/15] Revert "Update go.mod" This reverts commit 760fb8c4273010b2d5edcdd19e019358603999fb. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ac891b9..15bcd7c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/Cacsjep/go-astiav +module github.com/asticode/go-astiav go 1.17 From 004bbcd526b55b5fefd19e2ed51df497fed59dff Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Mon, 22 Jan 2024 18:24:35 +0100 Subject: [PATCH 04/15] Renaming to AllocSwsContext and remove ChangeResolution this should handled by users --- sws_context.go | 7 +------ sws_context_test.go | 15 +++++---------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/sws_context.go b/sws_context.go index 7f47575..654522c 100644 --- a/sws_context.go +++ b/sws_context.go @@ -34,7 +34,7 @@ const ( SWS_SPLINE = C.SWS_SPLINE ) -func CreateSwsContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags int, dstFrame *Frame) *SWSContext { +func AllocSwsContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags int, dstFrame *Frame) *SWSContext { dstFrame.SetPixelFormat(dstFormat) dstFrame.SetWidth(dstW) dstFrame.SetHeight(dstH) @@ -56,11 +56,6 @@ func CreateSwsContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dst return &SWSContext{c: swsCtx, dstFormat: dstFormat, srcFormat: srcFormat, srcW: srcW, srcH: srcH, dstW: dstW, dstH: dstH, flags: flags, dstFrame: dstFrame} } -func (sc *SWSContext) ChangeResolution(dstW, dstH int) *SWSContext { - sc.Free() - return CreateSwsContext(sc.srcW, sc.srcH, sc.srcFormat, dstW, dstH, sc.dstFormat, sc.flags, sc.dstFrame) -} - func (sc *SWSContext) Scale(srcFrame, dstFrame *Frame) error { height := int( C.sws_scale( diff --git a/sws_context_test.go b/sws_context_test.go index 52ccd8a..a67b42c 100644 --- a/sws_context_test.go +++ b/sws_context_test.go @@ -42,28 +42,23 @@ func TestSWS(t *testing.T) { srcFrame.ImageFillBlack() // Fill the source frame with black for testing // Create SWSContext for scaling and verify it's not nil - swsc := astiav.CreateSwsContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, astiav.SWS_BILINEAR, dstFrame) + swsc := astiav.AllocSwsContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, astiav.SWS_BILINEAR, dstFrame) require.NotNil(t, swsc) // Perform scaling and verify no errors err := swsc.Scale(srcFrame, dstFrame) require.NoError(t, err) - // Change resolution and perform scaling again - swsc = swsc.ChangeResolution(secondDstW, secondDstH) - err2 := swsc.Scale(srcFrame, dstFrame) - require.NoError(t, err2) - // Verify the dimensions and format of the destination frame - require.Equal(t, secondDstH, dstFrame.Height()) - require.Equal(t, secondDstW, dstFrame.Width()) + require.Equal(t, dstW, dstFrame.Height()) + require.Equal(t, dstH, dstFrame.Width()) require.Equal(t, dstFormat, dstFrame.PixelFormat()) // Convert frame data to image and perform additional verifications i1, err := dstFrame.Data().Image() require.NoError(t, err) - require.Equal(t, secondDstW, i1.Bounds().Dx()) - require.Equal(t, secondDstH, i1.Bounds().Dy()) + require.Equal(t, dstW, i1.Bounds().Dx()) + require.Equal(t, dstH, i1.Bounds().Dy()) assertImageType(t, i1, reflect.TypeOf((*image.NRGBA)(nil))) swsc.Free() } From e66d6040a3e8747bb3dac3b424a7817da81463a6 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Mon, 22 Jan 2024 18:27:27 +0100 Subject: [PATCH 05/15] update example to use new name and remove ChangeResolution --- examples/scaling/main.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index 4bb62e2..1538b17 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -99,7 +99,6 @@ func main() { } sws_created := false - reso_changed := false var sws *astiav.SWSContext dstFrame := astiav.AllocFrame() @@ -136,8 +135,9 @@ func main() { log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err)) } + // SWS context alloc on first frame otherwise we dont no the frame w,h and pixel format if !sws_created { - sws = astiav.CreateSwsContext(f.Width(), f.Height(), f.PixelFormat(), 480, 270, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) + sws = astiav.AllocSwsContext(f.Width(), f.Height(), f.PixelFormat(), 480, 270, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) sws_created = true } @@ -148,12 +148,6 @@ func main() { // Do something with decoded frame log.Printf("orig frame: %dx%d %s", f.Width(), f.Height(), f.PixelFormat().String()) log.Printf("scaled frame: %dx%d %s", dstFrame.Width(), dstFrame.Height(), dstFrame.PixelFormat().String()) - - // Change reso after first scaled frame just for demo - if !reso_changed { - sws = sws.ChangeResolution(100, 100) - reso_changed = true - } } } From cbe5a74624ed2aa51a1afbafa6d5fd59b3a0239e Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Tue, 23 Jan 2024 18:11:54 +0100 Subject: [PATCH 06/15] Follow scaling example from libav, update readme, improve sws Change scaling example to an similar libav example Update readme Add func to UpdateScalingParameters Rename AllocSwsContext to SwsGetContext Using a type for scaling algos/flags --- README.md | 1 + examples/scaling/main.go | 178 +++++++++++---------------------------- sws_context.go | 60 ++++++++++--- sws_context_test.go | 32 +++---- 4 files changed, 113 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 1758ea3..2bd212d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Examples are located in the [examples](examples) directory and mirror as much as |Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/filtering_video.c) |Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/remuxing.c) |Transcoding|[see](examples/transcoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/transcoding.c) +|Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/scaling_video.c) *Tip: you can use the video sample located in the `testdata` directory for your tests* diff --git a/examples/scaling/main.go b/examples/scaling/main.go index 1538b17..6f2acf5 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -1,158 +1,76 @@ package main import ( - "errors" "flag" "fmt" + "image/png" "log" - "strings" + "os" "github.com/asticode/go-astiav" ) -var ( - input = flag.String("i", "", "the input path") -) - -type stream struct { - decCodec *astiav.Codec - decCodecContext *astiav.CodecContext - inputStream *astiav.Stream -} - func main() { - // Handle ffmpeg logs - astiav.SetLogLevel(astiav.LogLevelDebug) - astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) { - log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l) - }) - - // Parse flags + var ( + dstFilename string + dstWidth int + dstHeight int + ) + + flag.StringVar(&dstFilename, "output", "", "Output file name") + flag.IntVar(&dstWidth, "w", 0, "Destination width") + flag.IntVar(&dstHeight, "h", 0, "Destination height") flag.Parse() - // Usage - if *input == "" { - log.Println("Usage: -i ") - return - } - - // Alloc packet - pkt := astiav.AllocPacket() - defer pkt.Free() - - // Alloc frame - f := astiav.AllocFrame() - defer f.Free() - - // Alloc input format context - inputFormatContext := astiav.AllocFormatContext() - if inputFormatContext == nil { - log.Fatal(errors.New("main: input format context is nil")) - } - defer inputFormatContext.Free() - - // Open input - if err := inputFormatContext.OpenInput(*input, nil, nil); err != nil { - log.Fatal(fmt.Errorf("main: opening input failed: %w", err)) - } - defer inputFormatContext.CloseInput() - - // Find stream info - if err := inputFormatContext.FindStreamInfo(nil); err != nil { - log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err)) + if dstFilename == "" || dstWidth <= 0 || dstHeight <= 0 { + fmt.Fprintf(os.Stderr, "Usage: %s -output output_file -w W -h H\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(1) } - // Loop through streams - streams := make(map[int]*stream) // Indexed by input stream index - for _, is := range inputFormatContext.Streams() { - // Only process audio or video - if is.CodecParameters().MediaType() != astiav.MediaTypeVideo { - continue - } - - // Create stream - s := &stream{inputStream: is} - - // Find decoder - if s.decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()); s.decCodec == nil { - log.Fatal(errors.New("main: codec is nil")) - } - - // Alloc codec context - if s.decCodecContext = astiav.AllocCodecContext(s.decCodec); s.decCodecContext == nil { - log.Fatal(errors.New("main: codec context is nil")) - } - defer s.decCodecContext.Free() - - // Update codec context - if err := is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil { - log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err)) - } - - // Open codec context - if err := s.decCodecContext.Open(s.decCodec, nil); err != nil { - log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err)) - } - - // Add stream - streams[is.Index()] = s + dstFile, err := os.Create(dstFilename) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not open destination file %s\n", dstFilename) + os.Exit(1) } - - sws_created := false - var sws *astiav.SWSContext + defer dstFile.Close() + + srcW, srcH := 320, 240 + srcPixFmt, dstPixFmt := astiav.PixelFormatYuv420P, astiav.PixelFormatRgba + srcFrame := astiav.AllocFrame() + srcFrame.SetHeight(srcH) + srcFrame.SetWidth(srcW) + srcFrame.SetPixelFormat(srcPixFmt) + srcFrame.AllocBuffer(1) + srcFrame.ImageFillBlack() + defer srcFrame.Free() dstFrame := astiav.AllocFrame() defer dstFrame.Free() - // Loop through packets - for { - // Read frame - if err := inputFormatContext.ReadFrame(pkt); err != nil { - if errors.Is(err, astiav.ErrEof) { - break - } - log.Fatal(fmt.Errorf("main: reading frame failed: %w", err)) - } - - // Get stream - s, ok := streams[pkt.StreamIndex()] - if !ok { - continue - } + swsCtx := astiav.SwsGetContext(srcW, srcH, srcPixFmt, dstWidth, dstHeight, dstPixFmt, astiav.SWS_POINT, dstFrame) + if swsCtx == nil { + fmt.Fprintln(os.Stderr, "Unable to create scale context") + os.Exit(1) + } + defer swsCtx.Free() - // Send packet - if err := s.decCodecContext.SendPacket(pkt); err != nil { - log.Fatal(fmt.Errorf("main: sending packet failed: %w", err)) - } + err = swsCtx.Scale(srcFrame, dstFrame) - // Loop - for { - // Receive frame - if err := s.decCodecContext.ReceiveFrame(f); err != nil { - if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { - break - } - log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err)) - } + if err != nil { + log.Fatalf("Unable to scale: %w", err) + } - // SWS context alloc on first frame otherwise we dont no the frame w,h and pixel format - if !sws_created { - sws = astiav.AllocSwsContext(f.Width(), f.Height(), f.PixelFormat(), 480, 270, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) - sws_created = true - } + img, err := dstFrame.Data().Image() - err := sws.Scale(f, dstFrame) - if err != nil { - log.Println("Scaling fails") - } else { - // Do something with decoded frame - log.Printf("orig frame: %dx%d %s", f.Width(), f.Height(), f.PixelFormat().String()) - log.Printf("scaled frame: %dx%d %s", dstFrame.Width(), dstFrame.Height(), dstFrame.PixelFormat().String()) - } + if err != nil { + log.Fatalf("Unable to get image: %w", err) + } - } + err = png.Encode(dstFile, img) + if err != nil { + log.Fatalf("Unable to encode image to png: %w", err) } - // Success - log.Println("success") + log.Printf("Successfully scale to %dx%d and write image to: %s", dstWidth, dstHeight, dstFilename) } diff --git a/sws_context.go b/sws_context.go index 654522c..d65242b 100644 --- a/sws_context.go +++ b/sws_context.go @@ -16,25 +16,28 @@ type SWSContext struct { srcH int dstW int dstH int - flags int + flags ScalingAlgorithm dstFrame *Frame } +// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale.h#L59 +type ScalingAlgorithm int + const ( - SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR - SWS_BILINEAR = C.SWS_BILINEAR - SWS_BICUBIC = C.SWS_BICUBIC - SWS_X = C.SWS_X - SWS_POINT = C.SWS_POINT - SWS_AREA = C.SWS_AREA - SWS_BICUBLIN = C.SWS_BICUBLIN - SWS_GAUSS = C.SWS_GAUSS - SWS_SINC = C.SWS_SINC - SWS_LANCZOS = C.SWS_LANCZOS - SWS_SPLINE = C.SWS_SPLINE + SWS_FAST_BILINEAR ScalingAlgorithm = ScalingAlgorithm(C.SWS_FAST_BILINEAR) + SWS_BILINEAR ScalingAlgorithm = ScalingAlgorithm(C.SWS_BILINEAR) + SWS_BICUBIC ScalingAlgorithm = ScalingAlgorithm(C.SWS_BICUBIC) + SWS_X ScalingAlgorithm = ScalingAlgorithm(C.SWS_X) + SWS_POINT ScalingAlgorithm = ScalingAlgorithm(C.SWS_POINT) + SWS_AREA ScalingAlgorithm = ScalingAlgorithm(C.SWS_AREA) + SWS_BICUBLIN ScalingAlgorithm = ScalingAlgorithm(C.SWS_BICUBLIN) + SWS_GAUSS ScalingAlgorithm = ScalingAlgorithm(C.SWS_GAUSS) + SWS_SINC ScalingAlgorithm = ScalingAlgorithm(C.SWS_SINC) + SWS_LANCZOS ScalingAlgorithm = ScalingAlgorithm(C.SWS_LANCZOS) + SWS_SPLINE ScalingAlgorithm = ScalingAlgorithm(C.SWS_SPLINE) ) -func AllocSwsContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags int, dstFrame *Frame) *SWSContext { +func SwsGetContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags ScalingAlgorithm, dstFrame *Frame) *SWSContext { dstFrame.SetPixelFormat(dstFormat) dstFrame.SetWidth(dstW) dstFrame.SetHeight(dstH) @@ -72,6 +75,37 @@ func (sc *SWSContext) Scale(srcFrame, dstFrame *Frame) error { return nil } +func (sc *SWSContext) UpdateScalingParameters(dstW, dstH int, dstFormat PixelFormat) error { + if sc.dstW != dstW || sc.dstH != dstH || sc.dstFormat != dstFormat { + sc.dstW = dstW + sc.dstH = dstH + sc.dstFormat = dstFormat + + // Reallocate the destination frame buffer + sc.dstFrame.SetPixelFormat(dstFormat) + sc.dstFrame.SetWidth(dstW) + sc.dstFrame.SetHeight(dstH) + sc.dstFrame.AllocBuffer(1) + + // Update the sws context + sc.c = C.sws_getCachedContext( + sc.c, + C.int(sc.srcW), + C.int(sc.srcH), + C.enum_AVPixelFormat(sc.srcFormat), + C.int(dstW), + C.int(dstH), + C.enum_AVPixelFormat(dstFormat), + C.int(sc.flags), + nil, nil, nil, + ) + if sc.c == nil { + return fmt.Errorf("failed to update sws context") + } + } + return nil +} + func (sc *SWSContext) Free() { C.sws_freeContext(sc.c) } diff --git a/sws_context_test.go b/sws_context_test.go index a67b42c..c966921 100644 --- a/sws_context_test.go +++ b/sws_context_test.go @@ -11,14 +11,10 @@ import ( // Test constants for source and destination dimensions and formats const ( - srcW = 100 - srcH = 100 - dstW = 200 - dstH = 200 - secondDstW = 300 - secondDstH = 300 - srcFormat = astiav.PixelFormatYuv420P - dstFormat = astiav.PixelFormatRgba + srcW = 100 + srcH = 100 + dstW = 200 + dstH = 200 ) // assertImageType is a helper function to check the type of an image. @@ -37,22 +33,18 @@ func TestSWS(t *testing.T) { srcFrame.SetHeight(srcH) srcFrame.SetWidth(srcW) - srcFrame.SetPixelFormat(srcFormat) + srcFrame.SetPixelFormat(astiav.PixelFormatYuv420P) srcFrame.AllocBuffer(1) - srcFrame.ImageFillBlack() // Fill the source frame with black for testing - // Create SWSContext for scaling and verify it's not nil - swsc := astiav.AllocSwsContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, astiav.SWS_BILINEAR, dstFrame) + swsc := astiav.SwsGetContext(srcW, srcH, astiav.PixelFormatYuv420P, dstW, dstH, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) require.NotNil(t, swsc) - // Perform scaling and verify no errors err := swsc.Scale(srcFrame, dstFrame) require.NoError(t, err) - // Verify the dimensions and format of the destination frame require.Equal(t, dstW, dstFrame.Height()) require.Equal(t, dstH, dstFrame.Width()) - require.Equal(t, dstFormat, dstFrame.PixelFormat()) + require.Equal(t, astiav.PixelFormatRgba, dstFrame.PixelFormat()) // Convert frame data to image and perform additional verifications i1, err := dstFrame.Data().Image() @@ -60,5 +52,15 @@ func TestSWS(t *testing.T) { require.Equal(t, dstW, i1.Bounds().Dx()) require.Equal(t, dstH, i1.Bounds().Dy()) assertImageType(t, i1, reflect.TypeOf((*image.NRGBA)(nil))) + + // Update sws ctx tests + err = swsc.UpdateScalingParameters(50, 50, astiav.PixelFormatRgb24) + require.NoError(t, err) + require.Equal(t, astiav.PixelFormatRgb24, dstFrame.PixelFormat()) + err = swsc.Scale(srcFrame, dstFrame) + require.NoError(t, err) + require.Equal(t, dstFrame.Width(), 50) + require.Equal(t, dstFrame.Height(), 50) + swsc.Free() } From 08aa9f2a62bbf09e85b02cdd22b0907bd1f5e132 Mon Sep 17 00:00:00 2001 From: Cacsjep <98898150+Cacsjep@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:08:54 +0100 Subject: [PATCH 07/15] Fix Errors in scale example --- examples/scaling/main.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index 6f2acf5..4f16588 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -56,21 +56,19 @@ func main() { defer swsCtx.Free() err = swsCtx.Scale(srcFrame, dstFrame) - if err != nil { - log.Fatalf("Unable to scale: %w", err) - } + log.Fatalf("Unable to scale: %s", err.Error()) + } - img, err := dstFrame.Data().Image() + img, err := dstFrame.Data().Image() + if err != nil { + log.Fatalf("Unable to get image: %s", err.Error()) + } - if err != nil { - log.Fatalf("Unable to get image: %w", err) - } - - err = png.Encode(dstFile, img) - if err != nil { - log.Fatalf("Unable to encode image to png: %w", err) - } + err = png.Encode(dstFile, img) + if err != nil { + log.Fatalf("Unable to encode image to png: %s", err.Error()) + } log.Printf("Successfully scale to %dx%d and write image to: %s", dstWidth, dstHeight, dstFilename) } From 124b0fa1316654488d51d9943b997ae7f507ec33 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Thu, 25 Jan 2024 10:11:03 +0100 Subject: [PATCH 08/15] Review Changes Now using sws context flags Restructer sws context and adding ned simpler methods to update the ctx Update the example Update the test --- README.md | 2 +- examples/scaling/main.go | 82 +++++++++++++---------- flags.go | 20 ++++++ flags_test.go | 9 +++ internal/cmd/flags/main.go | 1 + software_scale_context.go | 118 +++++++++++++++++++++++++++++++++ software_scale_context_flag.go | 22 ++++++ software_scale_context_test.go | 83 +++++++++++++++++++++++ sws_context.go | 111 ------------------------------- sws_context_test.go | 66 ------------------ 10 files changed, 300 insertions(+), 214 deletions(-) create mode 100644 software_scale_context.go create mode 100644 software_scale_context_flag.go create mode 100644 software_scale_context_test.go delete mode 100644 sws_context.go delete mode 100644 sws_context_test.go diff --git a/README.md b/README.md index 1ca43eb..80def99 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ Examples are located in the [examples](examples) directory and mirror as much as |Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/filtering_video.c) |Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/hw_decode.c) |Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/remuxing.c) -|Transcoding|[see](examples/transcoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/transcoding.c) |Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/scaling_video.c) +|Transcoding|[see](examples/transcoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/transcoding.c) *Tip: you can use the video sample located in the `testdata` directory for your tests* diff --git a/examples/scaling/main.go b/examples/scaling/main.go index 4f16588..f646033 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -11,64 +11,74 @@ import ( ) func main() { + var ( - dstFilename string - dstWidth int - dstHeight int + output = flag.String("o", "", "the png output path") + dstWidth = flag.Int("w", 50, "destination width") + dstHeight = flag.Int("h", 50, "destination height") ) - flag.StringVar(&dstFilename, "output", "", "Output file name") - flag.IntVar(&dstWidth, "w", 0, "Destination width") - flag.IntVar(&dstHeight, "h", 0, "Destination height") + // Parse flags flag.Parse() - if dstFilename == "" || dstWidth <= 0 || dstHeight <= 0 { - fmt.Fprintf(os.Stderr, "Usage: %s -output output_file -w W -h H\n", os.Args[0]) - flag.PrintDefaults() - os.Exit(1) + // Usage + if *output == "" || *dstWidth <= 0 || *dstHeight <= 0 { + log.Println("Usage: -o -w -h ") + return } - dstFile, err := os.Create(dstFilename) + // Create destination file + dstFile, err := os.Create(*output) if err != nil { - fmt.Fprintf(os.Stderr, "Could not open destination file %s\n", dstFilename) - os.Exit(1) + log.Fatal(fmt.Errorf("main: creating %s failed: %w", *output, err)) } defer dstFile.Close() - srcW, srcH := 320, 240 - srcPixFmt, dstPixFmt := astiav.PixelFormatYuv420P, astiav.PixelFormatRgba + // Create source frame srcFrame := astiav.AllocFrame() - srcFrame.SetHeight(srcH) - srcFrame.SetWidth(srcW) - srcFrame.SetPixelFormat(srcPixFmt) - srcFrame.AllocBuffer(1) - srcFrame.ImageFillBlack() defer srcFrame.Free() + srcFrame.SetWidth(320) + srcFrame.SetHeight(240) + srcFrame.SetPixelFormat(astiav.PixelFormatYuv420P) + if err = srcFrame.AllocBuffer(1); err != nil { + log.Fatal(fmt.Errorf("main: allocating source frame buffer failed: %w", err)) + } + if err = srcFrame.ImageFillBlack(); err != nil { + log.Fatal(fmt.Errorf("main: filling source frame with black image failed: %w", err)) + } + // Create destination frame dstFrame := astiav.AllocFrame() defer dstFrame.Free() - swsCtx := astiav.SwsGetContext(srcW, srcH, srcPixFmt, dstWidth, dstHeight, dstPixFmt, astiav.SWS_POINT, dstFrame) + // Create software scale context flags + swscf := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) + + // Create software scale context + swsCtx := astiav.NewSoftwareScaleContext(srcFrame.Width(), srcFrame.Height(), srcFrame.PixelFormat(), *dstWidth, *dstHeight, astiav.PixelFormatRgba, swscf) if swsCtx == nil { - fmt.Fprintln(os.Stderr, "Unable to create scale context") - os.Exit(1) + log.Fatal("main: creating software scale context failed") } defer swsCtx.Free() - err = swsCtx.Scale(srcFrame, dstFrame) - if err != nil { - log.Fatalf("Unable to scale: %s", err.Error()) - } + // Prepare destination frame (Width, Height and Buffer for correct scaling would be set) + swsCtx.PrepareDestinationFrameForScaling(dstFrame) - img, err := dstFrame.Data().Image() - if err != nil { - log.Fatalf("Unable to get image: %s", err.Error()) - } + // Scale frame + if output_slice_height := swsCtx.ScaleFrame(srcFrame, dstFrame); output_slice_height != *dstHeight { + log.Fatal(fmt.Errorf("main: scale error, expected output slice height %d, but got %d", *dstHeight, output_slice_height)) + } - err = png.Encode(dstFile, img) - if err != nil { - log.Fatalf("Unable to encode image to png: %s", err.Error()) - } + // Get image + img, err := dstFrame.Data().Image() + if err != nil { + log.Fatal(fmt.Errorf("main: getting destination image failed: %w", err)) + } + + // Encode to png + if err = png.Encode(dstFile, img); err != nil { + log.Fatal(fmt.Errorf("main: encoding to png failed: %w", err)) + } - log.Printf("Successfully scale to %dx%d and write image to: %s", dstWidth, dstHeight, dstFilename) + log.Println("done") } diff --git a/flags.go b/flags.go index 2761e17..339deec 100644 --- a/flags.go +++ b/flags.go @@ -304,3 +304,23 @@ func (fs StreamEventFlags) Del(f StreamEventFlag) StreamEventFlags { } func (fs StreamEventFlags) Has(f StreamEventFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } + +type SoftwareScaleContextFlags astikit.BitFlags + +func NewSoftwareScaleContextFlags(fs ...SoftwareScaleContextFlag) SoftwareScaleContextFlags { + o := SoftwareScaleContextFlags(0) + for _, f := range fs { + o = o.Add(f) + } + return o +} + +func (fs SoftwareScaleContextFlags) Add(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { + return SoftwareScaleContextFlags(astikit.BitFlags(fs).Add(uint64(f))) +} + +func (fs SoftwareScaleContextFlags) Del(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { + return SoftwareScaleContextFlags(astikit.BitFlags(fs).Del(uint64(f))) +} + +func (fs SoftwareScaleContextFlags) Has(f SoftwareScaleContextFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } diff --git a/flags_test.go b/flags_test.go index c133e68..e1a07f6 100644 --- a/flags_test.go +++ b/flags_test.go @@ -141,3 +141,12 @@ func TestStreamEventFlags(t *testing.T) { fs = fs.Del(astiav.StreamEventFlag(2)) require.False(t, fs.Has(astiav.StreamEventFlag(2))) } + +func TestSoftwareScaleContextFlags(t *testing.T) { + fs := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextFlag(1)) + require.True(t, fs.Has(astiav.SoftwareScaleContextFlag(1))) + fs = fs.Add(astiav.SoftwareScaleContextFlag(2)) + require.True(t, fs.Has(astiav.SoftwareScaleContextFlag(2))) + fs = fs.Del(astiav.SoftwareScaleContextFlag(2)) + require.False(t, fs.Has(astiav.SoftwareScaleContextFlag(2))) +} diff --git a/internal/cmd/flags/main.go b/internal/cmd/flags/main.go index 1e5c1a1..dfca6df 100644 --- a/internal/cmd/flags/main.go +++ b/internal/cmd/flags/main.go @@ -29,6 +29,7 @@ var list = []listItem{ {Name: "Packet"}, {Name: "Seek"}, {Name: "StreamEvent"}, + {Name: "SoftwareScaleContext"}, } var tmpl = `// Code generated by astiav. DO NOT EDIT. diff --git a/software_scale_context.go b/software_scale_context.go new file mode 100644 index 0000000..f5d8edb --- /dev/null +++ b/software_scale_context.go @@ -0,0 +1,118 @@ +package astiav + +//#cgo pkg-config: libswscale +//#include +import "C" +import ( + "fmt" +) + +// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale_internal.h#L300 +type SoftwareScaleContext struct { + c *C.struct_SwsContext + // We need to store attributes in GO since C attributes are internal and therefore not accessible + dstFormat C.enum_AVPixelFormat + dstH C.int + dstW C.int + flags SoftwareScaleContextFlags + srcFormat C.enum_AVPixelFormat + srcH C.int + srcW C.int +} + +func NewSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags SoftwareScaleContextFlags) *SoftwareScaleContext { + ssc := SoftwareScaleContext{ + dstFormat: C.enum_AVPixelFormat(dstFormat), + dstH: C.int(dstH), + dstW: C.int(dstW), + flags: flags, + srcFormat: C.enum_AVPixelFormat(srcFormat), + srcH: C.int(srcH), + srcW: C.int(srcW), + } + + ssc.c = C.sws_getContext( + ssc.srcW, + ssc.srcH, + ssc.srcFormat, + ssc.dstW, + ssc.dstH, + ssc.dstFormat, + C.int(ssc.flags), + nil, nil, nil, + ) + if ssc.c == nil { + return nil + } + return &ssc +} + +func (ssc *SoftwareScaleContext) ScaleFrame(src, dst *Frame) (height int) { + height = int( + C.sws_scale( + ssc.c, + &src.c.data[0], + &src.c.linesize[0], + 0, + C.int(src.Height()), + &dst.c.data[0], &dst.c.linesize[0])) + return +} + +func (ssc *SoftwareScaleContext) updateContext() error { + ssc.c = C.sws_getContext( + ssc.srcW, + ssc.srcH, + ssc.srcFormat, + ssc.dstW, + ssc.dstH, + ssc.dstFormat, + C.int(ssc.flags), + nil, nil, nil, + ) + if ssc.c == nil { + return fmt.Errorf("failed to update sws context") + } + return nil +} + +func (ssc *SoftwareScaleContext) PrepareDestinationFrameForScaling(dstFrame *Frame) { + dstFrame.SetPixelFormat(PixelFormat(ssc.dstFormat)) + dstFrame.SetWidth(int(ssc.dstW)) + dstFrame.SetHeight(int(ssc.dstH)) + dstFrame.AllocBuffer(1) +} + +func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { + ssc.dstH = C.int(i) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { + ssc.dstW = C.int(i) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error { + ssc.dstFormat = C.enum_AVPixelFormat(p) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetSourceWidth(i int) error { + ssc.srcW = C.int(i) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetSourceHeight(i int) error { + ssc.srcH = C.int(i) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error { + ssc.srcFormat = C.enum_AVPixelFormat(p) + return ssc.updateContext() +} + +func (sc *SoftwareScaleContext) Free() { + C.sws_freeContext(sc.c) +} diff --git a/software_scale_context_flag.go b/software_scale_context_flag.go new file mode 100644 index 0000000..9e08788 --- /dev/null +++ b/software_scale_context_flag.go @@ -0,0 +1,22 @@ +package astiav + +//#cgo pkg-config: libswscale +//#include +import "C" + +type SoftwareScaleContextFlag int + +// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale.h#L59 +const ( + SoftwareScaleContextArea = SoftwareScaleContextFlag(C.SWS_AREA) + SoftwareScaleContextBicubic = SoftwareScaleContextFlag(C.SWS_BICUBIC) + SoftwareScaleContextBicublin = SoftwareScaleContextFlag(C.SWS_BICUBLIN) + SoftwareScaleContextBilinear = SoftwareScaleContextFlag(C.SWS_BILINEAR) + SoftwareScaleContextFastBilinear = SoftwareScaleContextFlag(C.SWS_FAST_BILINEAR) + SoftwareScaleContextGauss = SoftwareScaleContextFlag(C.SWS_GAUSS) + SoftwareScaleContextLanczos = SoftwareScaleContextFlag(C.SWS_LANCZOS) + SoftwareScaleContextPoint = SoftwareScaleContextFlag(C.SWS_POINT) + SoftwareScaleContextSinc = SoftwareScaleContextFlag(C.SWS_SINC) + SoftwareScaleContextSpline = SoftwareScaleContextFlag(C.SWS_SPLINE) + SoftwareScaleContextX = SoftwareScaleContextFlag(C.SWS_X) +) diff --git a/software_scale_context_test.go b/software_scale_context_test.go new file mode 100644 index 0000000..75c32b2 --- /dev/null +++ b/software_scale_context_test.go @@ -0,0 +1,83 @@ +package astiav_test + +import ( + "image" + "testing" + + "github.com/asticode/go-astiav" + "github.com/stretchr/testify/require" +) + +func TestSoftwareScaleContext(t *testing.T) { + f1 := astiav.AllocFrame() + require.NotNil(t, f1) + defer f1.Free() + + f2 := astiav.AllocFrame() + require.NotNil(t, f2) + defer f2.Free() + + f3 := astiav.AllocFrame() + require.NotNil(t, f3) + defer f3.Free() + + srcW := 100 + srcH := 100 + srcPixelFormat := astiav.PixelFormatYuv420P + dstW := 200 + dstH := 200 + dstPixelFormat := astiav.PixelFormatRgba + + f1.SetHeight(srcH) + f1.SetWidth(srcW) + f1.SetPixelFormat(srcPixelFormat) + require.NoError(t, f1.AllocBuffer(1)) + + swscf := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) + swsc := astiav.NewSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf) + require.NotNil(t, swsc) + + swsc.PrepareDestinationFrameForScaling(f2) + require.Equal(t, dstH, swsc.ScaleFrame(f1, f2)) + + require.Equal(t, dstW, f2.Height()) + require.Equal(t, dstH, f2.Width()) + require.Equal(t, dstPixelFormat, f2.PixelFormat()) + + i1, err := f2.Data().Image() + require.NoError(t, err) + require.Equal(t, dstW, i1.Bounds().Dx()) + require.Equal(t, dstH, i1.Bounds().Dy()) + _, nrgbaOk := i1.(*image.NRGBA) + require.True(t, nrgbaOk) + + dstW = 50 + dstH = 50 + dstPixelFormat = astiav.PixelFormatYuv420P + + require.NoError(t, swsc.SetSourceWidth(f2.Width())) + require.NoError(t, swsc.SetSourceHeight(f2.Height())) + require.NoError(t, swsc.SetSourcePixelFormat(f2.PixelFormat())) + + require.NoError(t, swsc.SetDestinationWidth(dstW)) + require.NoError(t, swsc.SetDestinationHeight(dstH)) + require.NoError(t, swsc.SetDestinationPixelFormat(dstPixelFormat)) + + swsc.PrepareDestinationFrameForScaling(f3) + require.Equal(t, f3.Height(), dstH) + require.Equal(t, f3.Width(), dstW) + require.Equal(t, f3.PixelFormat(), dstPixelFormat) + require.Equal(t, dstH, swsc.ScaleFrame(f2, f3)) + require.Equal(t, dstW, f3.Height()) + require.Equal(t, dstH, f3.Width()) + require.Equal(t, dstPixelFormat, f3.PixelFormat()) + + i2, err := f3.Data().Image() + require.NoError(t, err) + require.Equal(t, dstW, i2.Bounds().Dx()) + require.Equal(t, dstH, i2.Bounds().Dy()) + _, ycbcrOk := i2.(*image.YCbCr) + require.True(t, ycbcrOk) + + defer swsc.Free() +} diff --git a/sws_context.go b/sws_context.go deleted file mode 100644 index d65242b..0000000 --- a/sws_context.go +++ /dev/null @@ -1,111 +0,0 @@ -package astiav - -//#cgo pkg-config: libswscale -//#include -import "C" -import ( - "fmt" -) - -// https://github.com/FFmpeg/FFmpeg/blob/n4.2.7/libswscale/swscale_internal.h#L280 -type SWSContext struct { - c *C.struct_SwsContext - dstFormat PixelFormat - srcFormat PixelFormat - srcW int - srcH int - dstW int - dstH int - flags ScalingAlgorithm - dstFrame *Frame -} - -// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale.h#L59 -type ScalingAlgorithm int - -const ( - SWS_FAST_BILINEAR ScalingAlgorithm = ScalingAlgorithm(C.SWS_FAST_BILINEAR) - SWS_BILINEAR ScalingAlgorithm = ScalingAlgorithm(C.SWS_BILINEAR) - SWS_BICUBIC ScalingAlgorithm = ScalingAlgorithm(C.SWS_BICUBIC) - SWS_X ScalingAlgorithm = ScalingAlgorithm(C.SWS_X) - SWS_POINT ScalingAlgorithm = ScalingAlgorithm(C.SWS_POINT) - SWS_AREA ScalingAlgorithm = ScalingAlgorithm(C.SWS_AREA) - SWS_BICUBLIN ScalingAlgorithm = ScalingAlgorithm(C.SWS_BICUBLIN) - SWS_GAUSS ScalingAlgorithm = ScalingAlgorithm(C.SWS_GAUSS) - SWS_SINC ScalingAlgorithm = ScalingAlgorithm(C.SWS_SINC) - SWS_LANCZOS ScalingAlgorithm = ScalingAlgorithm(C.SWS_LANCZOS) - SWS_SPLINE ScalingAlgorithm = ScalingAlgorithm(C.SWS_SPLINE) -) - -func SwsGetContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags ScalingAlgorithm, dstFrame *Frame) *SWSContext { - dstFrame.SetPixelFormat(dstFormat) - dstFrame.SetWidth(dstW) - dstFrame.SetHeight(dstH) - dstFrame.AllocBuffer(1) - - swsCtx := C.sws_getContext( - C.int(srcW), - C.int(srcH), - C.enum_AVPixelFormat(srcFormat), - C.int(dstW), - C.int(dstH), - C.enum_AVPixelFormat(dstFormat), - C.int(flags), - nil, nil, nil, - ) - if swsCtx == nil { - return nil - } - return &SWSContext{c: swsCtx, dstFormat: dstFormat, srcFormat: srcFormat, srcW: srcW, srcH: srcH, dstW: dstW, dstH: dstH, flags: flags, dstFrame: dstFrame} -} - -func (sc *SWSContext) Scale(srcFrame, dstFrame *Frame) error { - height := int( - C.sws_scale( - sc.c, - &srcFrame.c.data[0], - &srcFrame.c.linesize[0], - 0, - C.int(srcFrame.Height()), - &dstFrame.c.data[0], &dstFrame.c.linesize[0])) - - if height != dstFrame.Height() { - return fmt.Errorf("sws_scale did not process all lines, expected: %d, got: %d", dstFrame.Height(), height) - } - return nil -} - -func (sc *SWSContext) UpdateScalingParameters(dstW, dstH int, dstFormat PixelFormat) error { - if sc.dstW != dstW || sc.dstH != dstH || sc.dstFormat != dstFormat { - sc.dstW = dstW - sc.dstH = dstH - sc.dstFormat = dstFormat - - // Reallocate the destination frame buffer - sc.dstFrame.SetPixelFormat(dstFormat) - sc.dstFrame.SetWidth(dstW) - sc.dstFrame.SetHeight(dstH) - sc.dstFrame.AllocBuffer(1) - - // Update the sws context - sc.c = C.sws_getCachedContext( - sc.c, - C.int(sc.srcW), - C.int(sc.srcH), - C.enum_AVPixelFormat(sc.srcFormat), - C.int(dstW), - C.int(dstH), - C.enum_AVPixelFormat(dstFormat), - C.int(sc.flags), - nil, nil, nil, - ) - if sc.c == nil { - return fmt.Errorf("failed to update sws context") - } - } - return nil -} - -func (sc *SWSContext) Free() { - C.sws_freeContext(sc.c) -} diff --git a/sws_context_test.go b/sws_context_test.go deleted file mode 100644 index c966921..0000000 --- a/sws_context_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package astiav_test - -import ( - "image" - "reflect" - "testing" - - "github.com/asticode/go-astiav" - "github.com/stretchr/testify/require" -) - -// Test constants for source and destination dimensions and formats -const ( - srcW = 100 - srcH = 100 - dstW = 200 - dstH = 200 -) - -// assertImageType is a helper function to check the type of an image. -func assertImageType(t *testing.T, img image.Image, expectedType reflect.Type) { - actualType := reflect.TypeOf(img) - require.Equal(t, expectedType, actualType, "Image type does not match") -} - -// TestSWS tests the scaling functionality provided by the SWSContext. -func TestSWS(t *testing.T) { - // Allocate and initialize source and destination frames - srcFrame := astiav.AllocFrame() - defer srcFrame.Free() - dstFrame := astiav.AllocFrame() - defer dstFrame.Free() - - srcFrame.SetHeight(srcH) - srcFrame.SetWidth(srcW) - srcFrame.SetPixelFormat(astiav.PixelFormatYuv420P) - srcFrame.AllocBuffer(1) - - swsc := astiav.SwsGetContext(srcW, srcH, astiav.PixelFormatYuv420P, dstW, dstH, astiav.PixelFormatRgba, astiav.SWS_BILINEAR, dstFrame) - require.NotNil(t, swsc) - - err := swsc.Scale(srcFrame, dstFrame) - require.NoError(t, err) - - require.Equal(t, dstW, dstFrame.Height()) - require.Equal(t, dstH, dstFrame.Width()) - require.Equal(t, astiav.PixelFormatRgba, dstFrame.PixelFormat()) - - // Convert frame data to image and perform additional verifications - i1, err := dstFrame.Data().Image() - require.NoError(t, err) - require.Equal(t, dstW, i1.Bounds().Dx()) - require.Equal(t, dstH, i1.Bounds().Dy()) - assertImageType(t, i1, reflect.TypeOf((*image.NRGBA)(nil))) - - // Update sws ctx tests - err = swsc.UpdateScalingParameters(50, 50, astiav.PixelFormatRgb24) - require.NoError(t, err) - require.Equal(t, astiav.PixelFormatRgb24, dstFrame.PixelFormat()) - err = swsc.Scale(srcFrame, dstFrame) - require.NoError(t, err) - require.Equal(t, dstFrame.Width(), 50) - require.Equal(t, dstFrame.Height(), 50) - - swsc.Free() -} From 2dcc530eea3c9385b53e5fa4702864980f9eb082 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Thu, 25 Jan 2024 10:24:36 +0100 Subject: [PATCH 09/15] Correctly handle error for buffer alloc in PrepareDestinationFrameForScaling --- examples/scaling/main.go | 4 +++- software_scale_context.go | 4 ++-- software_scale_context_test.go | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index f646033..ba4614a 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -62,7 +62,9 @@ func main() { defer swsCtx.Free() // Prepare destination frame (Width, Height and Buffer for correct scaling would be set) - swsCtx.PrepareDestinationFrameForScaling(dstFrame) + if err = swsCtx.PrepareDestinationFrameForScaling(dstFrame); err != nil { + log.Fatal(fmt.Errorf("main: prepare destination image failed: %w", err)) + } // Scale frame if output_slice_height := swsCtx.ScaleFrame(srcFrame, dstFrame); output_slice_height != *dstHeight { diff --git a/software_scale_context.go b/software_scale_context.go index f5d8edb..c4d5f7f 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -76,11 +76,11 @@ func (ssc *SoftwareScaleContext) updateContext() error { return nil } -func (ssc *SoftwareScaleContext) PrepareDestinationFrameForScaling(dstFrame *Frame) { +func (ssc *SoftwareScaleContext) PrepareDestinationFrameForScaling(dstFrame *Frame) error { dstFrame.SetPixelFormat(PixelFormat(ssc.dstFormat)) dstFrame.SetWidth(int(ssc.dstW)) dstFrame.SetHeight(int(ssc.dstH)) - dstFrame.AllocBuffer(1) + return dstFrame.AllocBuffer(1) } func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { diff --git a/software_scale_context_test.go b/software_scale_context_test.go index 75c32b2..90e2a2a 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -37,7 +37,7 @@ func TestSoftwareScaleContext(t *testing.T) { swsc := astiav.NewSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf) require.NotNil(t, swsc) - swsc.PrepareDestinationFrameForScaling(f2) + require.NoError(t, swsc.PrepareDestinationFrameForScaling(f2)) require.Equal(t, dstH, swsc.ScaleFrame(f1, f2)) require.Equal(t, dstW, f2.Height()) @@ -63,7 +63,7 @@ func TestSoftwareScaleContext(t *testing.T) { require.NoError(t, swsc.SetDestinationHeight(dstH)) require.NoError(t, swsc.SetDestinationPixelFormat(dstPixelFormat)) - swsc.PrepareDestinationFrameForScaling(f3) + require.NoError(t, swsc.PrepareDestinationFrameForScaling(f3)) require.Equal(t, f3.Height(), dstH) require.Equal(t, f3.Width(), dstW) require.Equal(t, f3.PixelFormat(), dstPixelFormat) From 5a50f317e1a44ad4ea398f8d1f89814ee3d83ed8 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Thu, 25 Jan 2024 10:52:01 +0100 Subject: [PATCH 10/15] Add more getter and setter for sws Get/Set source w,h,pixfmt Get/Set dst w,h,pixfmt Get/Set sws flags --- software_scale_context.go | 46 ++++++++++++++++++++++++++++------ software_scale_context_test.go | 15 +++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/software_scale_context.go b/software_scale_context.go index c4d5f7f..bf76c83 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -83,13 +83,36 @@ func (ssc *SoftwareScaleContext) PrepareDestinationFrameForScaling(dstFrame *Fra return dstFrame.AllocBuffer(1) } -func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { - ssc.dstH = C.int(i) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) DestinationHeight() int { + return int(ssc.dstH) } -func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { - ssc.dstW = C.int(i) +func (ssc *SoftwareScaleContext) DestinationPixelFormat() PixelFormat { + return PixelFormat(ssc.dstFormat) +} + +func (ssc *SoftwareScaleContext) DestinationWidth() int { + return int(ssc.dstW) +} + +func (ssc *SoftwareScaleContext) SourceHeight() int { + return int(ssc.srcH) +} + +func (ssc *SoftwareScaleContext) SourcePixelFormat() PixelFormat { + return PixelFormat(ssc.srcFormat) +} + +func (ssc *SoftwareScaleContext) SourceWidth() int { + return int(ssc.srcW) +} + +func (ssc *SoftwareScaleContext) Flags() SoftwareScaleContextFlags { + return ssc.flags +} + +func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { + ssc.dstH = C.int(i) return ssc.updateContext() } @@ -98,8 +121,8 @@ func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error return ssc.updateContext() } -func (ssc *SoftwareScaleContext) SetSourceWidth(i int) error { - ssc.srcW = C.int(i) +func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { + ssc.dstW = C.int(i) return ssc.updateContext() } @@ -113,6 +136,15 @@ func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error { return ssc.updateContext() } +func (ssc *SoftwareScaleContext) SetSourceWidth(i int) error { + ssc.srcW = C.int(i) + return ssc.updateContext() +} + +func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) { + ssc.flags = swscf +} + func (sc *SoftwareScaleContext) Free() { C.sws_freeContext(sc.c) } diff --git a/software_scale_context_test.go b/software_scale_context_test.go index 90e2a2a..4935566 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -33,9 +33,14 @@ func TestSoftwareScaleContext(t *testing.T) { f1.SetPixelFormat(srcPixelFormat) require.NoError(t, f1.AllocBuffer(1)) - swscf := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) - swsc := astiav.NewSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf) + swscf_1 := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) + swsc := astiav.NewSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf_1) require.NotNil(t, swsc) + require.Equal(t, swsc.Flags(), swscf_1) + + swscf_2 := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextPoint) + swsc.SetFlags(swscf_2) + require.Equal(t, swsc.Flags(), swscf_2) require.NoError(t, swsc.PrepareDestinationFrameForScaling(f2)) require.Equal(t, dstH, swsc.ScaleFrame(f1, f2)) @@ -56,12 +61,18 @@ func TestSoftwareScaleContext(t *testing.T) { dstPixelFormat = astiav.PixelFormatYuv420P require.NoError(t, swsc.SetSourceWidth(f2.Width())) + require.Equal(t, swsc.SourceWidth(), f2.Width()) require.NoError(t, swsc.SetSourceHeight(f2.Height())) + require.Equal(t, swsc.SourceHeight(), f2.Height()) require.NoError(t, swsc.SetSourcePixelFormat(f2.PixelFormat())) + require.Equal(t, swsc.SourcePixelFormat(), f2.PixelFormat()) require.NoError(t, swsc.SetDestinationWidth(dstW)) + require.Equal(t, swsc.DestinationWidth(), dstW) require.NoError(t, swsc.SetDestinationHeight(dstH)) + require.Equal(t, swsc.DestinationHeight(), dstH) require.NoError(t, swsc.SetDestinationPixelFormat(dstPixelFormat)) + require.Equal(t, swsc.DestinationPixelFormat(), dstPixelFormat) require.NoError(t, swsc.PrepareDestinationFrameForScaling(f3)) require.Equal(t, f3.Height(), dstH) From 7a4e56a7c01d5c9f91bfd68776f1277abc6daa0e Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Thu, 25 Jan 2024 11:07:37 +0100 Subject: [PATCH 11/15] Adding resolution get/set --- software_scale_context.go | 48 ++++++++++++++++++++++++---------- software_scale_context_test.go | 10 +++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/software_scale_context.go b/software_scale_context.go index bf76c83..d6701ca 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -91,20 +91,12 @@ func (ssc *SoftwareScaleContext) DestinationPixelFormat() PixelFormat { return PixelFormat(ssc.dstFormat) } -func (ssc *SoftwareScaleContext) DestinationWidth() int { - return int(ssc.dstW) -} - -func (ssc *SoftwareScaleContext) SourceHeight() int { - return int(ssc.srcH) -} - -func (ssc *SoftwareScaleContext) SourcePixelFormat() PixelFormat { - return PixelFormat(ssc.srcFormat) +func (ssc *SoftwareScaleContext) DestinationResolution() (int, int) { + return int(ssc.dstW), int(ssc.dstH) } -func (ssc *SoftwareScaleContext) SourceWidth() int { - return int(ssc.srcW) +func (ssc *SoftwareScaleContext) DestinationWidth() int { + return int(ssc.dstW) } func (ssc *SoftwareScaleContext) Flags() SoftwareScaleContextFlags { @@ -121,11 +113,21 @@ func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error return ssc.updateContext() } +func (ssc *SoftwareScaleContext) SetDestinationResolution(w int, h int) error { + ssc.dstW = C.int(w) + ssc.dstH = C.int(h) + return ssc.updateContext() +} + func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { ssc.dstW = C.int(i) return ssc.updateContext() } +func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) { + ssc.flags = swscf +} + func (ssc *SoftwareScaleContext) SetSourceHeight(i int) error { ssc.srcH = C.int(i) return ssc.updateContext() @@ -136,13 +138,31 @@ func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error { return ssc.updateContext() } +func (ssc *SoftwareScaleContext) SetSourceResolution(w int, h int) error { + ssc.srcW = C.int(w) + ssc.srcH = C.int(h) + return ssc.updateContext() +} + func (ssc *SoftwareScaleContext) SetSourceWidth(i int) error { ssc.srcW = C.int(i) return ssc.updateContext() } -func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) { - ssc.flags = swscf +func (ssc *SoftwareScaleContext) SourceHeight() int { + return int(ssc.srcH) +} + +func (ssc *SoftwareScaleContext) SourcePixelFormat() PixelFormat { + return PixelFormat(ssc.srcFormat) +} + +func (ssc *SoftwareScaleContext) SourceResolution() (int, int) { + return int(ssc.srcW), int(ssc.srcH) +} + +func (ssc *SoftwareScaleContext) SourceWidth() int { + return int(ssc.srcW) } func (sc *SoftwareScaleContext) Free() { diff --git a/software_scale_context_test.go b/software_scale_context_test.go index 4935566..d25d592 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -90,5 +90,15 @@ func TestSoftwareScaleContext(t *testing.T) { _, ycbcrOk := i2.(*image.YCbCr) require.True(t, ycbcrOk) + require.NoError(t, swsc.SetDestinationResolution(640, 480)) + w, h := swsc.DestinationResolution() + require.Equal(t, w, 640) + require.Equal(t, h, 480) + + require.NoError(t, swsc.SetSourceResolution(480, 270)) + w, h = swsc.SourceResolution() + require.Equal(t, w, 480) + require.Equal(t, h, 270) + defer swsc.Free() } From bb41cd169456f1dc12b87440f4916c15898462d2 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Thu, 25 Jan 2024 11:13:56 +0100 Subject: [PATCH 12/15] Use CachedContext when updating sws ctx --- software_scale_context.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software_scale_context.go b/software_scale_context.go index d6701ca..e1622e0 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -60,7 +60,8 @@ func (ssc *SoftwareScaleContext) ScaleFrame(src, dst *Frame) (height int) { } func (ssc *SoftwareScaleContext) updateContext() error { - ssc.c = C.sws_getContext( + ssc.c = C.sws_getCachedContext( + ssc.c, ssc.srcW, ssc.srcH, ssc.srcFormat, From 4724734870d86e2242e19619b5948f14b5b3220e Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Sat, 27 Jan 2024 20:38:43 +0100 Subject: [PATCH 13/15] Review changes Reorder flags, and update them # Update Example Use renaming function to create sws context Clean up Use new framdata image funcs # Sws scale context New way to update the context Use sws_scale_frame instead of sws_scale Reordering funcs for get and set # Sws sclate context flag Add "Flag" for algo name # Update sws test --- examples/scaling/main.go | 58 ++++++---- flags.go | 64 +++++------ flags_test.go | 36 +++--- internal/cmd/flags/main.go | 4 +- software_scale_context.go | 194 ++++++++++++++++++++------------- software_scale_context_flag.go | 22 ++-- software_scale_context_test.go | 83 ++++++-------- 7 files changed, 249 insertions(+), 212 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index ba4614a..f76045f 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -6,17 +6,23 @@ import ( "image/png" "log" "os" + "strings" "github.com/asticode/go-astiav" ) -func main() { +var ( + output = flag.String("o", "", "the png output path") + dstWidth = flag.Int("w", 50, "destination width") + dstHeight = flag.Int("h", 50, "destination height") +) - var ( - output = flag.String("o", "", "the png output path") - dstWidth = flag.Int("w", 50, "destination width") - dstHeight = flag.Int("h", 50, "destination height") - ) +func main() { + // Handle ffmpeg logs + astiav.SetLogLevel(astiav.LogLevelDebug) + astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) { + log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l) + }) // Parse flags flag.Parse() @@ -51,30 +57,35 @@ func main() { dstFrame := astiav.AllocFrame() defer dstFrame.Free() - // Create software scale context flags - swscf := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) - // Create software scale context - swsCtx := astiav.NewSoftwareScaleContext(srcFrame.Width(), srcFrame.Height(), srcFrame.PixelFormat(), *dstWidth, *dstHeight, astiav.PixelFormatRgba, swscf) - if swsCtx == nil { - log.Fatal("main: creating software scale context failed") + swsCtx, err := astiav.CreateSoftwareScaleContext( + srcFrame.Width(), + srcFrame.Height(), + srcFrame.PixelFormat(), + *dstWidth, + *dstHeight, + astiav.PixelFormatRgba, + astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextFlagBilinear), + ) + if err != nil { + log.Fatal(fmt.Errorf("main: creating software scale context failed: %w"), err) } defer swsCtx.Free() - // Prepare destination frame (Width, Height and Buffer for correct scaling would be set) - if err = swsCtx.PrepareDestinationFrameForScaling(dstFrame); err != nil { - log.Fatal(fmt.Errorf("main: prepare destination image failed: %w", err)) - } - // Scale frame - if output_slice_height := swsCtx.ScaleFrame(srcFrame, dstFrame); output_slice_height != *dstHeight { - log.Fatal(fmt.Errorf("main: scale error, expected output slice height %d, but got %d", *dstHeight, output_slice_height)) + if err := swsCtx.ScaleFrame(srcFrame, dstFrame); err != nil { + log.Fatal(fmt.Errorf("main: scale frame failed: %w"), err) } - // Get image - img, err := dstFrame.Data().Image() + // Guess destination image format + img, err := dstFrame.Data().GuessImageFormat() if err != nil { - log.Fatal(fmt.Errorf("main: getting destination image failed: %w", err)) + log.Fatal(fmt.Errorf("main: guessing destination image format failed: %w", err)) + } + + // Copy frame data to destination image + if err = dstFrame.Data().ToImage(img); err != nil { + log.Fatal(fmt.Errorf("main: copying frame data to destination image failed: %w", err)) } // Encode to png @@ -82,5 +93,6 @@ func main() { log.Fatal(fmt.Errorf("main: encoding to png failed: %w", err)) } - log.Println("done") + // Success + log.Println("success") } diff --git a/flags.go b/flags.go index 339deec..6007100 100644 --- a/flags.go +++ b/flags.go @@ -145,45 +145,45 @@ func (fs FilterCommandFlags) Del(f FilterCommandFlag) FilterCommandFlags { func (fs FilterCommandFlags) Has(f FilterCommandFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } -type FormatContextCtxFlags astikit.BitFlags +type FormatContextFlags astikit.BitFlags -func NewFormatContextCtxFlags(fs ...FormatContextCtxFlag) FormatContextCtxFlags { - o := FormatContextCtxFlags(0) +func NewFormatContextFlags(fs ...FormatContextFlag) FormatContextFlags { + o := FormatContextFlags(0) for _, f := range fs { o = o.Add(f) } return o } -func (fs FormatContextCtxFlags) Add(f FormatContextCtxFlag) FormatContextCtxFlags { - return FormatContextCtxFlags(astikit.BitFlags(fs).Add(uint64(f))) +func (fs FormatContextFlags) Add(f FormatContextFlag) FormatContextFlags { + return FormatContextFlags(astikit.BitFlags(fs).Add(uint64(f))) } -func (fs FormatContextCtxFlags) Del(f FormatContextCtxFlag) FormatContextCtxFlags { - return FormatContextCtxFlags(astikit.BitFlags(fs).Del(uint64(f))) +func (fs FormatContextFlags) Del(f FormatContextFlag) FormatContextFlags { + return FormatContextFlags(astikit.BitFlags(fs).Del(uint64(f))) } -func (fs FormatContextCtxFlags) Has(f FormatContextCtxFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } +func (fs FormatContextFlags) Has(f FormatContextFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } -type FormatContextFlags astikit.BitFlags +type FormatContextCtxFlags astikit.BitFlags -func NewFormatContextFlags(fs ...FormatContextFlag) FormatContextFlags { - o := FormatContextFlags(0) +func NewFormatContextCtxFlags(fs ...FormatContextCtxFlag) FormatContextCtxFlags { + o := FormatContextCtxFlags(0) for _, f := range fs { o = o.Add(f) } return o } -func (fs FormatContextFlags) Add(f FormatContextFlag) FormatContextFlags { - return FormatContextFlags(astikit.BitFlags(fs).Add(uint64(f))) +func (fs FormatContextCtxFlags) Add(f FormatContextCtxFlag) FormatContextCtxFlags { + return FormatContextCtxFlags(astikit.BitFlags(fs).Add(uint64(f))) } -func (fs FormatContextFlags) Del(f FormatContextFlag) FormatContextFlags { - return FormatContextFlags(astikit.BitFlags(fs).Del(uint64(f))) +func (fs FormatContextCtxFlags) Del(f FormatContextCtxFlag) FormatContextCtxFlags { + return FormatContextCtxFlags(astikit.BitFlags(fs).Del(uint64(f))) } -func (fs FormatContextFlags) Has(f FormatContextFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } +func (fs FormatContextCtxFlags) Has(f FormatContextCtxFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } type FormatEventFlags astikit.BitFlags @@ -285,42 +285,42 @@ func (fs SeekFlags) Del(f SeekFlag) SeekFlags { func (fs SeekFlags) Has(f SeekFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } -type StreamEventFlags astikit.BitFlags +type SoftwareScaleContextFlags astikit.BitFlags -func NewStreamEventFlags(fs ...StreamEventFlag) StreamEventFlags { - o := StreamEventFlags(0) +func NewSoftwareScaleContextFlags(fs ...SoftwareScaleContextFlag) SoftwareScaleContextFlags { + o := SoftwareScaleContextFlags(0) for _, f := range fs { o = o.Add(f) } return o } -func (fs StreamEventFlags) Add(f StreamEventFlag) StreamEventFlags { - return StreamEventFlags(astikit.BitFlags(fs).Add(uint64(f))) +func (fs SoftwareScaleContextFlags) Add(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { + return SoftwareScaleContextFlags(astikit.BitFlags(fs).Add(uint64(f))) } -func (fs StreamEventFlags) Del(f StreamEventFlag) StreamEventFlags { - return StreamEventFlags(astikit.BitFlags(fs).Del(uint64(f))) +func (fs SoftwareScaleContextFlags) Del(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { + return SoftwareScaleContextFlags(astikit.BitFlags(fs).Del(uint64(f))) } -func (fs StreamEventFlags) Has(f StreamEventFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } +func (fs SoftwareScaleContextFlags) Has(f SoftwareScaleContextFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } -type SoftwareScaleContextFlags astikit.BitFlags +type StreamEventFlags astikit.BitFlags -func NewSoftwareScaleContextFlags(fs ...SoftwareScaleContextFlag) SoftwareScaleContextFlags { - o := SoftwareScaleContextFlags(0) +func NewStreamEventFlags(fs ...StreamEventFlag) StreamEventFlags { + o := StreamEventFlags(0) for _, f := range fs { o = o.Add(f) } return o } -func (fs SoftwareScaleContextFlags) Add(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { - return SoftwareScaleContextFlags(astikit.BitFlags(fs).Add(uint64(f))) +func (fs StreamEventFlags) Add(f StreamEventFlag) StreamEventFlags { + return StreamEventFlags(astikit.BitFlags(fs).Add(uint64(f))) } -func (fs SoftwareScaleContextFlags) Del(f SoftwareScaleContextFlag) SoftwareScaleContextFlags { - return SoftwareScaleContextFlags(astikit.BitFlags(fs).Del(uint64(f))) +func (fs StreamEventFlags) Del(f StreamEventFlag) StreamEventFlags { + return StreamEventFlags(astikit.BitFlags(fs).Del(uint64(f))) } -func (fs SoftwareScaleContextFlags) Has(f SoftwareScaleContextFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } +func (fs StreamEventFlags) Has(f StreamEventFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } diff --git a/flags_test.go b/flags_test.go index e1a07f6..d6189cf 100644 --- a/flags_test.go +++ b/flags_test.go @@ -70,15 +70,6 @@ func TestFilterCommandFlags(t *testing.T) { require.False(t, fs.Has(astiav.FilterCommandFlag(2))) } -func TestFormatContextCtxFlags(t *testing.T) { - fs := astiav.NewFormatContextCtxFlags(astiav.FormatContextCtxFlag(1)) - require.True(t, fs.Has(astiav.FormatContextCtxFlag(1))) - fs = fs.Add(astiav.FormatContextCtxFlag(2)) - require.True(t, fs.Has(astiav.FormatContextCtxFlag(2))) - fs = fs.Del(astiav.FormatContextCtxFlag(2)) - require.False(t, fs.Has(astiav.FormatContextCtxFlag(2))) -} - func TestFormatContextFlags(t *testing.T) { fs := astiav.NewFormatContextFlags(astiav.FormatContextFlag(1)) require.True(t, fs.Has(astiav.FormatContextFlag(1))) @@ -88,6 +79,15 @@ func TestFormatContextFlags(t *testing.T) { require.False(t, fs.Has(astiav.FormatContextFlag(2))) } +func TestFormatContextCtxFlags(t *testing.T) { + fs := astiav.NewFormatContextCtxFlags(astiav.FormatContextCtxFlag(1)) + require.True(t, fs.Has(astiav.FormatContextCtxFlag(1))) + fs = fs.Add(astiav.FormatContextCtxFlag(2)) + require.True(t, fs.Has(astiav.FormatContextCtxFlag(2))) + fs = fs.Del(astiav.FormatContextCtxFlag(2)) + require.False(t, fs.Has(astiav.FormatContextCtxFlag(2))) +} + func TestFormatEventFlags(t *testing.T) { fs := astiav.NewFormatEventFlags(astiav.FormatEventFlag(1)) require.True(t, fs.Has(astiav.FormatEventFlag(1))) @@ -133,15 +133,6 @@ func TestSeekFlags(t *testing.T) { require.False(t, fs.Has(astiav.SeekFlag(2))) } -func TestStreamEventFlags(t *testing.T) { - fs := astiav.NewStreamEventFlags(astiav.StreamEventFlag(1)) - require.True(t, fs.Has(astiav.StreamEventFlag(1))) - fs = fs.Add(astiav.StreamEventFlag(2)) - require.True(t, fs.Has(astiav.StreamEventFlag(2))) - fs = fs.Del(astiav.StreamEventFlag(2)) - require.False(t, fs.Has(astiav.StreamEventFlag(2))) -} - func TestSoftwareScaleContextFlags(t *testing.T) { fs := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextFlag(1)) require.True(t, fs.Has(astiav.SoftwareScaleContextFlag(1))) @@ -150,3 +141,12 @@ func TestSoftwareScaleContextFlags(t *testing.T) { fs = fs.Del(astiav.SoftwareScaleContextFlag(2)) require.False(t, fs.Has(astiav.SoftwareScaleContextFlag(2))) } + +func TestStreamEventFlags(t *testing.T) { + fs := astiav.NewStreamEventFlags(astiav.StreamEventFlag(1)) + require.True(t, fs.Has(astiav.StreamEventFlag(1))) + fs = fs.Add(astiav.StreamEventFlag(2)) + require.True(t, fs.Has(astiav.StreamEventFlag(2))) + fs = fs.Del(astiav.StreamEventFlag(2)) + require.False(t, fs.Has(astiav.StreamEventFlag(2))) +} diff --git a/internal/cmd/flags/main.go b/internal/cmd/flags/main.go index dfca6df..c5f05bf 100644 --- a/internal/cmd/flags/main.go +++ b/internal/cmd/flags/main.go @@ -21,15 +21,15 @@ var list = []listItem{ {Name: "CodecHardwareConfigMethod"}, {Name: "Dictionary"}, {Name: "FilterCommand"}, - {Name: "FormatContextCtx"}, {Name: "FormatContext"}, + {Name: "FormatContextCtx"}, {Name: "FormatEvent"}, {Name: "IOContext"}, {Name: "IOFormat"}, {Name: "Packet"}, {Name: "Seek"}, - {Name: "StreamEvent"}, {Name: "SoftwareScaleContext"}, + {Name: "StreamEvent"}, } var tmpl = `// Code generated by astiav. DO NOT EDIT. diff --git a/software_scale_context.go b/software_scale_context.go index e1622e0..e9e1ffd 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -4,7 +4,7 @@ package astiav //#include import "C" import ( - "fmt" + "errors" ) // https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale_internal.h#L300 @@ -20,7 +20,17 @@ type SoftwareScaleContext struct { srcW C.int } -func NewSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags SoftwareScaleContextFlags) *SoftwareScaleContext { +type softwareScaleContextUpdate struct { + dstFormat *PixelFormat + dstH *int + dstW *int + flags *SoftwareScaleContextFlags + srcFormat *PixelFormat + srcH *int + srcW *int +} + +func CreateSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH int, dstFormat PixelFormat, flags SoftwareScaleContextFlags) (*SoftwareScaleContext, error) { ssc := SoftwareScaleContext{ dstFormat: C.enum_AVPixelFormat(dstFormat), dstH: C.int(dstH), @@ -42,130 +52,166 @@ func NewSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dstH i nil, nil, nil, ) if ssc.c == nil { - return nil + return nil, errors.New("astiav: empty new context") } - return &ssc + return &ssc, nil } -func (ssc *SoftwareScaleContext) ScaleFrame(src, dst *Frame) (height int) { - height = int( - C.sws_scale( - ssc.c, - &src.c.data[0], - &src.c.linesize[0], - 0, - C.int(src.Height()), - &dst.c.data[0], &dst.c.linesize[0])) - return +func (ssc *SoftwareScaleContext) ScaleFrame(src, dst *Frame) error { + return newError(C.sws_scale_frame(ssc.c, dst.c, src.c)) } -func (ssc *SoftwareScaleContext) updateContext() error { - ssc.c = C.sws_getCachedContext( +func (ssc *SoftwareScaleContext) update(u softwareScaleContextUpdate) error { + dstW := ssc.dstW + if u.dstW != nil { + dstW = C.int(*u.dstW) + } + + dstH := ssc.dstH + if u.dstH != nil { + dstH = C.int(*u.dstH) + } + + dstFormat := ssc.dstFormat + if u.dstFormat != nil { + dstFormat = C.enum_AVPixelFormat(*u.dstFormat) + } + + srcW := ssc.srcW + if u.srcW != nil { + srcW = C.int(*u.srcW) + } + + srcH := ssc.srcH + if u.srcH != nil { + srcH = C.int(*u.srcH) + } + + srcFormat := ssc.srcFormat + if u.srcFormat != nil { + srcFormat = C.enum_AVPixelFormat(*u.srcFormat) + } + + flags := ssc.flags + if u.flags != nil { + flags = SoftwareScaleContextFlags(*u.flags) + } + + c := C.sws_getCachedContext( ssc.c, - ssc.srcW, - ssc.srcH, - ssc.srcFormat, - ssc.dstW, - ssc.dstH, - ssc.dstFormat, - C.int(ssc.flags), + srcW, + srcH, + srcFormat, + dstW, + dstH, + dstFormat, + C.int(flags), nil, nil, nil, ) - if ssc.c == nil { - return fmt.Errorf("failed to update sws context") + if c == nil { + return errors.New("astiav: empty new context") } - return nil -} -func (ssc *SoftwareScaleContext) PrepareDestinationFrameForScaling(dstFrame *Frame) error { - dstFrame.SetPixelFormat(PixelFormat(ssc.dstFormat)) - dstFrame.SetWidth(int(ssc.dstW)) - dstFrame.SetHeight(int(ssc.dstH)) - return dstFrame.AllocBuffer(1) -} + ssc.c = c + if u.dstW != nil { + ssc.dstW = dstW + } + if u.dstH != nil { + ssc.dstH = dstH + } + if u.dstFormat != nil { + ssc.dstFormat = dstFormat + } + if u.srcW != nil { + ssc.srcW = srcW + } + if u.srcH != nil { + ssc.srcH = srcH + } + if u.srcFormat != nil { + ssc.srcFormat = srcFormat + } + if u.flags != nil { + ssc.flags = flags + } -func (ssc *SoftwareScaleContext) DestinationHeight() int { - return int(ssc.dstH) + return nil } -func (ssc *SoftwareScaleContext) DestinationPixelFormat() PixelFormat { - return PixelFormat(ssc.dstFormat) +func (ssc *SoftwareScaleContext) Flags() SoftwareScaleContextFlags { + return ssc.flags } -func (ssc *SoftwareScaleContext) DestinationResolution() (int, int) { - return int(ssc.dstW), int(ssc.dstH) +func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) error { + return ssc.update(softwareScaleContextUpdate{flags: &swscf}) } func (ssc *SoftwareScaleContext) DestinationWidth() int { return int(ssc.dstW) } -func (ssc *SoftwareScaleContext) Flags() SoftwareScaleContextFlags { - return ssc.flags -} - -func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { - ssc.dstH = C.int(i) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { + return ssc.update(softwareScaleContextUpdate{dstW: &i}) } -func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error { - ssc.dstFormat = C.enum_AVPixelFormat(p) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) DestinationHeight() int { + return int(ssc.dstH) } -func (ssc *SoftwareScaleContext) SetDestinationResolution(w int, h int) error { - ssc.dstW = C.int(w) - ssc.dstH = C.int(h) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) SetDestinationHeight(i int) error { + return ssc.update(softwareScaleContextUpdate{dstH: &i}) } -func (ssc *SoftwareScaleContext) SetDestinationWidth(i int) error { - ssc.dstW = C.int(i) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) DestinationPixelFormat() PixelFormat { + return PixelFormat(ssc.dstFormat) } -func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) { - ssc.flags = swscf +func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error { + return ssc.update(softwareScaleContextUpdate{dstFormat: &p}) } -func (ssc *SoftwareScaleContext) SetSourceHeight(i int) error { - ssc.srcH = C.int(i) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) DestinationResolution() (width int, height int) { + return int(ssc.dstW), int(ssc.dstH) } -func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error { - ssc.srcFormat = C.enum_AVPixelFormat(p) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) SetDestinationResolution(w int, h int) error { + return ssc.update(softwareScaleContextUpdate{dstW: &w, dstH: &h}) } -func (ssc *SoftwareScaleContext) SetSourceResolution(w int, h int) error { - ssc.srcW = C.int(w) - ssc.srcH = C.int(h) - return ssc.updateContext() +func (ssc *SoftwareScaleContext) SourceWidth() int { + return int(ssc.srcW) } func (ssc *SoftwareScaleContext) SetSourceWidth(i int) error { - ssc.srcW = C.int(i) - return ssc.updateContext() + return ssc.update(softwareScaleContextUpdate{srcW: &i}) } func (ssc *SoftwareScaleContext) SourceHeight() int { return int(ssc.srcH) } +func (ssc *SoftwareScaleContext) SetSourceHeight(i int) error { + return ssc.update(softwareScaleContextUpdate{srcH: &i}) +} + func (ssc *SoftwareScaleContext) SourcePixelFormat() PixelFormat { return PixelFormat(ssc.srcFormat) } +func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error { + return ssc.update(softwareScaleContextUpdate{srcFormat: &p}) +} + func (ssc *SoftwareScaleContext) SourceResolution() (int, int) { return int(ssc.srcW), int(ssc.srcH) } -func (ssc *SoftwareScaleContext) SourceWidth() int { - return int(ssc.srcW) +func (ssc *SoftwareScaleContext) SetSourceResolution(w int, h int) error { + return ssc.update(softwareScaleContextUpdate{srcW: &w, srcH: &h}) } -func (sc *SoftwareScaleContext) Free() { - C.sws_freeContext(sc.c) +func (ssc *SoftwareScaleContext) Free() { + if ssc.c != nil { + C.sws_freeContext(ssc.c) + } } diff --git a/software_scale_context_flag.go b/software_scale_context_flag.go index 9e08788..079b403 100644 --- a/software_scale_context_flag.go +++ b/software_scale_context_flag.go @@ -8,15 +8,15 @@ type SoftwareScaleContextFlag int // https://github.com/FFmpeg/FFmpeg/blob/n5.0/libswscale/swscale.h#L59 const ( - SoftwareScaleContextArea = SoftwareScaleContextFlag(C.SWS_AREA) - SoftwareScaleContextBicubic = SoftwareScaleContextFlag(C.SWS_BICUBIC) - SoftwareScaleContextBicublin = SoftwareScaleContextFlag(C.SWS_BICUBLIN) - SoftwareScaleContextBilinear = SoftwareScaleContextFlag(C.SWS_BILINEAR) - SoftwareScaleContextFastBilinear = SoftwareScaleContextFlag(C.SWS_FAST_BILINEAR) - SoftwareScaleContextGauss = SoftwareScaleContextFlag(C.SWS_GAUSS) - SoftwareScaleContextLanczos = SoftwareScaleContextFlag(C.SWS_LANCZOS) - SoftwareScaleContextPoint = SoftwareScaleContextFlag(C.SWS_POINT) - SoftwareScaleContextSinc = SoftwareScaleContextFlag(C.SWS_SINC) - SoftwareScaleContextSpline = SoftwareScaleContextFlag(C.SWS_SPLINE) - SoftwareScaleContextX = SoftwareScaleContextFlag(C.SWS_X) + SoftwareScaleContextFlagArea = SoftwareScaleContextFlag(C.SWS_AREA) + SoftwareScaleContextFlagBicubic = SoftwareScaleContextFlag(C.SWS_BICUBIC) + SoftwareScaleContextFlagBicublin = SoftwareScaleContextFlag(C.SWS_BICUBLIN) + SoftwareScaleContextFlagBilinear = SoftwareScaleContextFlag(C.SWS_BILINEAR) + SoftwareScaleContextFlagFastBilinear = SoftwareScaleContextFlag(C.SWS_FAST_BILINEAR) + SoftwareScaleContextFlagGauss = SoftwareScaleContextFlag(C.SWS_GAUSS) + SoftwareScaleContextFlagLanczos = SoftwareScaleContextFlag(C.SWS_LANCZOS) + SoftwareScaleContextFlagPoint = SoftwareScaleContextFlag(C.SWS_POINT) + SoftwareScaleContextFlagSinc = SoftwareScaleContextFlag(C.SWS_SINC) + SoftwareScaleContextFlagSpline = SoftwareScaleContextFlag(C.SWS_SPLINE) + SoftwareScaleContextFlagX = SoftwareScaleContextFlag(C.SWS_X) ) diff --git a/software_scale_context_test.go b/software_scale_context_test.go index d25d592..37d7e74 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -1,7 +1,6 @@ package astiav_test import ( - "image" "testing" "github.com/asticode/go-astiav" @@ -21,49 +20,46 @@ func TestSoftwareScaleContext(t *testing.T) { require.NotNil(t, f3) defer f3.Free() - srcW := 100 - srcH := 100 + srcW := 320 + srcH := 280 srcPixelFormat := astiav.PixelFormatYuv420P - dstW := 200 - dstH := 200 + dstW := 640 + dstH := 480 dstPixelFormat := astiav.PixelFormatRgba + swscf1 := astiav.SoftwareScaleContextFlags(astiav.SoftwareScaleContextFlagBilinear) f1.SetHeight(srcH) f1.SetWidth(srcW) f1.SetPixelFormat(srcPixelFormat) require.NoError(t, f1.AllocBuffer(1)) - swscf_1 := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear) - swsc := astiav.NewSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf_1) - require.NotNil(t, swsc) - require.Equal(t, swsc.Flags(), swscf_1) - - swscf_2 := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextPoint) - swsc.SetFlags(swscf_2) - require.Equal(t, swsc.Flags(), swscf_2) - - require.NoError(t, swsc.PrepareDestinationFrameForScaling(f2)) - require.Equal(t, dstH, swsc.ScaleFrame(f1, f2)) + swsc, err := astiav.CreateSoftwareScaleContext(srcW, srcH, srcPixelFormat, dstW, dstH, dstPixelFormat, swscf1) + require.NoError(t, err) + defer swsc.Free() - require.Equal(t, dstW, f2.Height()) - require.Equal(t, dstH, f2.Width()) + require.NoError(t, swsc.ScaleFrame(f1, f2)) + require.Equal(t, dstH, f2.Height()) + require.Equal(t, dstW, f2.Width()) require.Equal(t, dstPixelFormat, f2.PixelFormat()) - i1, err := f2.Data().Image() - require.NoError(t, err) - require.Equal(t, dstW, i1.Bounds().Dx()) - require.Equal(t, dstH, i1.Bounds().Dy()) - _, nrgbaOk := i1.(*image.NRGBA) - require.True(t, nrgbaOk) - - dstW = 50 - dstH = 50 + dstW = 1024 + dstH = 576 dstPixelFormat = astiav.PixelFormatYuv420P + swscf2 := astiav.SoftwareScaleContextFlags(astiav.SoftwareScaleContextFlagPoint) + + require.Equal(t, swsc.Flags(), swscf1) + swsc.SetFlags(swscf2) + require.Equal(t, swsc.Flags(), swscf2) require.NoError(t, swsc.SetSourceWidth(f2.Width())) require.Equal(t, swsc.SourceWidth(), f2.Width()) require.NoError(t, swsc.SetSourceHeight(f2.Height())) require.Equal(t, swsc.SourceHeight(), f2.Height()) + require.NoError(t, swsc.SetSourceResolution(1280, 720)) + w, h := swsc.SourceResolution() + require.Equal(t, w, 1280) + require.Equal(t, h, 720) + require.NoError(t, swsc.SetSourceResolution(f2.Width(), f2.Height())) require.NoError(t, swsc.SetSourcePixelFormat(f2.PixelFormat())) require.Equal(t, swsc.SourcePixelFormat(), f2.PixelFormat()) @@ -71,34 +67,17 @@ func TestSoftwareScaleContext(t *testing.T) { require.Equal(t, swsc.DestinationWidth(), dstW) require.NoError(t, swsc.SetDestinationHeight(dstH)) require.Equal(t, swsc.DestinationHeight(), dstH) + require.NoError(t, swsc.SetDestinationResolution(800, 600)) + w, h = swsc.DestinationResolution() + require.Equal(t, w, 800) + require.Equal(t, h, 600) + require.NoError(t, swsc.SetDestinationResolution(dstW, dstH)) require.NoError(t, swsc.SetDestinationPixelFormat(dstPixelFormat)) require.Equal(t, swsc.DestinationPixelFormat(), dstPixelFormat) - require.NoError(t, swsc.PrepareDestinationFrameForScaling(f3)) - require.Equal(t, f3.Height(), dstH) - require.Equal(t, f3.Width(), dstW) - require.Equal(t, f3.PixelFormat(), dstPixelFormat) - require.Equal(t, dstH, swsc.ScaleFrame(f2, f3)) - require.Equal(t, dstW, f3.Height()) - require.Equal(t, dstH, f3.Width()) + require.NoError(t, swsc.ScaleFrame(f2, f3)) + require.Equal(t, dstW, f3.Width()) + require.Equal(t, dstH, f3.Height()) require.Equal(t, dstPixelFormat, f3.PixelFormat()) - i2, err := f3.Data().Image() - require.NoError(t, err) - require.Equal(t, dstW, i2.Bounds().Dx()) - require.Equal(t, dstH, i2.Bounds().Dy()) - _, ycbcrOk := i2.(*image.YCbCr) - require.True(t, ycbcrOk) - - require.NoError(t, swsc.SetDestinationResolution(640, 480)) - w, h := swsc.DestinationResolution() - require.Equal(t, w, 640) - require.Equal(t, h, 480) - - require.NoError(t, swsc.SetSourceResolution(480, 270)) - w, h = swsc.SourceResolution() - require.Equal(t, w, 480) - require.Equal(t, h, 270) - - defer swsc.Free() } From ebc95cdc5803a7b5788f79ee0767d94e44aaa897 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Sat, 27 Jan 2024 20:42:42 +0100 Subject: [PATCH 14/15] fix fmt error args bug --- examples/scaling/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index f76045f..5257079 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -68,13 +68,13 @@ func main() { astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextFlagBilinear), ) if err != nil { - log.Fatal(fmt.Errorf("main: creating software scale context failed: %w"), err) + log.Fatal(fmt.Errorf("main: creating software scale context failed: %w", err)) } defer swsCtx.Free() // Scale frame if err := swsCtx.ScaleFrame(srcFrame, dstFrame); err != nil { - log.Fatal(fmt.Errorf("main: scale frame failed: %w"), err) + log.Fatal(fmt.Errorf("main: scale frame failed: %w", err)) } // Guess destination image format From d0b980a8d7312ad0a6aa9f3f64365213cebb9d79 Mon Sep 17 00:00:00 2001 From: Cacsjep Date: Mon, 29 Jan 2024 18:23:39 +0100 Subject: [PATCH 15/15] Review Changes Simpfy sws update Use c.int for sws flags update test --- examples/scaling/main.go | 2 +- software_scale_context.go | 38 +++++++++++----------------------- software_scale_context_test.go | 17 +++++++++------ 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/examples/scaling/main.go b/examples/scaling/main.go index 5257079..e8921ba 100644 --- a/examples/scaling/main.go +++ b/examples/scaling/main.go @@ -74,7 +74,7 @@ func main() { // Scale frame if err := swsCtx.ScaleFrame(srcFrame, dstFrame); err != nil { - log.Fatal(fmt.Errorf("main: scale frame failed: %w", err)) + log.Fatal(fmt.Errorf("main: scaling frame failed: %w", err)) } // Guess destination image format diff --git a/software_scale_context.go b/software_scale_context.go index e9e1ffd..96ddea8 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -14,7 +14,7 @@ type SoftwareScaleContext struct { dstFormat C.enum_AVPixelFormat dstH C.int dstW C.int - flags SoftwareScaleContextFlags + flags C.int srcFormat C.enum_AVPixelFormat srcH C.int srcW C.int @@ -35,7 +35,7 @@ func CreateSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dst dstFormat: C.enum_AVPixelFormat(dstFormat), dstH: C.int(dstH), dstW: C.int(dstW), - flags: flags, + flags: C.int(flags), srcFormat: C.enum_AVPixelFormat(srcFormat), srcH: C.int(srcH), srcW: C.int(srcW), @@ -94,7 +94,7 @@ func (ssc *SoftwareScaleContext) update(u softwareScaleContextUpdate) error { flags := ssc.flags if u.flags != nil { - flags = SoftwareScaleContextFlags(*u.flags) + flags = C.int(*u.flags) } c := C.sws_getCachedContext( @@ -105,7 +105,7 @@ func (ssc *SoftwareScaleContext) update(u softwareScaleContextUpdate) error { dstW, dstH, dstFormat, - C.int(flags), + flags, nil, nil, nil, ) if c == nil { @@ -113,33 +113,19 @@ func (ssc *SoftwareScaleContext) update(u softwareScaleContextUpdate) error { } ssc.c = c - if u.dstW != nil { - ssc.dstW = dstW - } - if u.dstH != nil { - ssc.dstH = dstH - } - if u.dstFormat != nil { - ssc.dstFormat = dstFormat - } - if u.srcW != nil { - ssc.srcW = srcW - } - if u.srcH != nil { - ssc.srcH = srcH - } - if u.srcFormat != nil { - ssc.srcFormat = srcFormat - } - if u.flags != nil { - ssc.flags = flags - } + ssc.dstW = dstW + ssc.dstH = dstH + ssc.dstFormat = dstFormat + ssc.srcW = srcW + ssc.srcH = srcH + ssc.srcFormat = srcFormat + ssc.flags = flags return nil } func (ssc *SoftwareScaleContext) Flags() SoftwareScaleContextFlags { - return ssc.flags + return SoftwareScaleContextFlags(ssc.flags) } func (ssc *SoftwareScaleContext) SetFlags(swscf SoftwareScaleContextFlags) error { diff --git a/software_scale_context_test.go b/software_scale_context_test.go index 37d7e74..c681736 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -55,22 +55,27 @@ func TestSoftwareScaleContext(t *testing.T) { require.Equal(t, swsc.SourceWidth(), f2.Width()) require.NoError(t, swsc.SetSourceHeight(f2.Height())) require.Equal(t, swsc.SourceHeight(), f2.Height()) - require.NoError(t, swsc.SetSourceResolution(1280, 720)) + + newSourceW := 1280 + newSourceH := 720 + require.NoError(t, swsc.SetSourceResolution(newSourceW, newSourceH)) w, h := swsc.SourceResolution() - require.Equal(t, w, 1280) - require.Equal(t, h, 720) + require.Equal(t, w, newSourceW) + require.Equal(t, h, newSourceH) require.NoError(t, swsc.SetSourceResolution(f2.Width(), f2.Height())) require.NoError(t, swsc.SetSourcePixelFormat(f2.PixelFormat())) require.Equal(t, swsc.SourcePixelFormat(), f2.PixelFormat()) + newDestW := 800 + newDestH := 600 require.NoError(t, swsc.SetDestinationWidth(dstW)) require.Equal(t, swsc.DestinationWidth(), dstW) require.NoError(t, swsc.SetDestinationHeight(dstH)) require.Equal(t, swsc.DestinationHeight(), dstH) - require.NoError(t, swsc.SetDestinationResolution(800, 600)) + require.NoError(t, swsc.SetDestinationResolution(newDestW, newDestH)) w, h = swsc.DestinationResolution() - require.Equal(t, w, 800) - require.Equal(t, h, 600) + require.Equal(t, w, newDestW) + require.Equal(t, h, newDestH) require.NoError(t, swsc.SetDestinationResolution(dstW, dstH)) require.NoError(t, swsc.SetDestinationPixelFormat(dstPixelFormat)) require.Equal(t, swsc.DestinationPixelFormat(), dstPixelFormat)