Skip to content

Commit

Permalink
Review Changes
Browse files Browse the repository at this point in the history
Now using sws context flags
Restructer sws context and adding ned simpler methods to update the ctx
Update the example
Update the test
  • Loading branch information
Cacsjep committed Jan 25, 2024
1 parent 0b75de3 commit 124b0fa
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 214 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down
82 changes: 46 additions & 36 deletions examples/scaling/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: <binary path> -o <output path> -w <output width> -h <output height>")
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")
}
20 changes: 20 additions & 0 deletions flags.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions flags_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/cmd/flags/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var list = []listItem{
{Name: "Packet"},
{Name: "Seek"},
{Name: "StreamEvent"},
{Name: "SoftwareScaleContext"},
}

var tmpl = `// Code generated by astiav. DO NOT EDIT.
Expand Down
118 changes: 118 additions & 0 deletions software_scale_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package astiav

//#cgo pkg-config: libswscale
//#include <libswscale/swscale.h>
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)
}
22 changes: 22 additions & 0 deletions software_scale_context_flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package astiav

//#cgo pkg-config: libswscale
//#include <libswscale/swscale.h>
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)
)
83 changes: 83 additions & 0 deletions software_scale_context_test.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading

0 comments on commit 124b0fa

Please sign in to comment.