Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation for SWS scale #33

Merged
merged 18 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
|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)
|Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/scaling_video.c)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
|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
86 changes: 86 additions & 0 deletions examples/scaling/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"flag"
"fmt"
"image/png"
"log"
"os"

"github.com/asticode/go-astiav"
)

func main() {

Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
var (
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
output = flag.String("o", "", "the png output path")
dstWidth = flag.Int("w", 50, "destination width")
dstHeight = flag.Int("h", 50, "destination height")
)

// Parse flags
flag.Parse()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved

// Usage
if *output == "" || *dstWidth <= 0 || *dstHeight <= 0 {
log.Println("Usage: <binary path> -o <output path> -w <output width> -h <output height>")
return
}

// Create destination file
dstFile, err := os.Create(*output)
if err != nil {
log.Fatal(fmt.Errorf("main: creating %s failed: %w", *output, err))
}
defer dstFile.Close()

// Create source frame
srcFrame := astiav.AllocFrame()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
defer srcFrame.Free()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
defer dstFrame.Free()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved

// Create software scale context flags
swscf := astiav.NewSoftwareScaleContextFlags(astiav.SoftwareScaleContextBilinear)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved

// 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")
}
defer swsCtx.Free()

// Prepare destination frame (Width, Height and Buffer for correct scaling would be set)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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))
}

// Get image
img, err := dstFrame.Data().Image()
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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.Println("done")
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
}
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"},
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
}

var tmpl = `// Code generated by astiav. DO NOT EDIT.
Expand Down
171 changes: 171 additions & 0 deletions software_scale_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
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
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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 {
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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) {
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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_getCachedContext(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 things not OK with the update logic:

  • ssc.c shouldn't be updated if there's an error here
  • in all setters, ssc attributes shouldn't be updated if there is an error

Here's what I'd rather have:

  • create a struct with pointers to Go types
type softwareScaleContextUpdate struct {
  dstW *int
  ...
}
  • rename updateContext to update and follow this signature func (ssc *SoftwareScaleContext) update(u softwareScaleContextUpdate) error
  • inside it, start by determining what would be the new attributes
dstW := ssc.dstW
if u.dstW != nil {
  dstW = *u.dstW
}
...
  • then create the new context
c := C.sws_getCachedContext(...)
if c == nil {
  return errors.New("astiav: empty new context")
}
  • and finish by updating the context:
ssc.c = c
if u.dstW != nil {
  ssc.dstW = *u.dstW
}
...

ssc.c,
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) error {
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
dstFrame.SetPixelFormat(PixelFormat(ssc.dstFormat))
dstFrame.SetWidth(int(ssc.dstW))
dstFrame.SetHeight(int(ssc.dstH))
return dstFrame.AllocBuffer(1)
}

func (ssc *SoftwareScaleContext) DestinationHeight() int {
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
return int(ssc.dstH)
}

func (ssc *SoftwareScaleContext) DestinationPixelFormat() PixelFormat {
return PixelFormat(ssc.dstFormat)
}

func (ssc *SoftwareScaleContext) DestinationResolution() (int, int) {
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
return int(ssc.dstW), int(ssc.dstH)
}

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)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
return ssc.updateContext()
}

func (ssc *SoftwareScaleContext) SetDestinationPixelFormat(p PixelFormat) error {
ssc.dstFormat = C.enum_AVPixelFormat(p)
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()
}

func (ssc *SoftwareScaleContext) SetSourcePixelFormat(p PixelFormat) error {
ssc.srcFormat = C.enum_AVPixelFormat(p)
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) 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() {
C.sws_freeContext(sc.c)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
}
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)
Cacsjep marked this conversation as resolved.
Show resolved Hide resolved
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)
)
Loading
Loading