Skip to content

Commit

Permalink
Added frame data SetBytes() and FromImage()
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Oct 13, 2024
1 parent 3e7aa4e commit a5fe16a
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 93 deletions.
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
|Custom IO Demuxing|[see](examples/custom_io_demuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/avio_reading.c)
|Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/filtering_video.c)
|Frame data manipulating|[see](examples/frame_data_manipulating/main.go)|X
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/hw_decode.c)
|Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/remuxing.c)
|Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/scaling_video.c)
Expand Down
33 changes: 2 additions & 31 deletions examples/demuxing_decoding/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"flag"
"fmt"
"image"
"log"
"strings"

Expand Down Expand Up @@ -107,7 +106,6 @@ func main() {
}

// Loop through packets
var i image.Image
for {
// Read frame
if err := inputFormatContext.ReadFrame(pkt); err != nil {
Expand Down Expand Up @@ -138,35 +136,8 @@ func main() {
log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err))
}

// Do something with decoded frame
if s.inputStream.CodecParameters().MediaType() == astiav.MediaTypeVideo {
// In this example, we'll process the frame data but you can do whatever you feel like
// with the decoded frame
fd := f.Data()

// Image has not yet been initialized
// If the image format can change in the stream, you'll need to guess image format for every frame
if i == nil {
// Guess image format
// It might not return an image.Image in the proper format for your use case, in that case
// you can skip this step and provide .ToImage() with your own image.Image
var err error
if i, err = fd.GuessImageFormat(); err != nil {
log.Fatal(fmt.Errorf("main: guessing image format failed: %w", err))
}
}

// Copy frame data to the image
if err := fd.ToImage(i); err != nil {
log.Fatal(fmt.Errorf("main: copying frame data to the image failed: %w", err))
}

// Log
log.Printf("new video frame: stream %d - pts: %d - size: %dx%d - color at (0,0): %+v", pkt.StreamIndex(), f.Pts(), i.Bounds().Dx(), i.Bounds().Dy(), i.At(0, 0))
} else {
// Log
log.Printf("new audio frame: stream %d - pts: %d", pkt.StreamIndex(), f.Pts())
}
// Log
log.Printf("new %s frame: stream %d - pts: %d", s.inputStream.CodecParameters().MediaType(), pkt.StreamIndex(), f.Pts())
}
}

Expand Down
108 changes: 108 additions & 0 deletions examples/frame_data_manipulating/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

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

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

const (
align = 1
pngPath = "testdata/image-rgba.png"
rawBufferPath = "testdata/image-rgba.rgba"
)

func main() {
// Handle ffmpeg logs
astiav.SetLogLevel(astiav.LogLevelDebug)
astiav.SetLogCallback(func(c astiav.Classer, l astiav.LogLevel, fmt, msg string) {
var cs string
if c != nil {
if cl := c.Class(); cl != nil {
cs = " - class: " + cl.String()
}
}
log.Printf("ffmpeg log: %s%s - level: %d\n", strings.TrimSpace(msg), cs, l)
})

// Alloc frames
f1 := astiav.AllocFrame()
defer f1.Free()
f2 := astiav.AllocFrame()
defer f2.Free()

// To write data manually into a frame, proper attributes need to be set and allocated
for _, f := range []*astiav.Frame{f1, f2} {
// Set attributes
f.SetHeight(256)
f.SetPixelFormat(astiav.PixelFormatRgba)
f.SetWidth(256)

// Alloc buffer
if err := f.AllocBuffer(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err))
}

// Alloc image
if err := f.AllocImage(align); err != nil {
log.Fatal(fmt.Errorf("main: allocating image failed: %w", err))
}
}

// When writing data manually into a frame, you usually need to make sure the frame is writable
// Don't forget this step above all if the frame's buffer is referenced elsewhere
for _, f := range []*astiav.Frame{f1, f2} {
// Make writable
if err := f.MakeWritable(); err != nil {
log.Fatal(fmt.Errorf("main: making frame writable failed: %w", err))
}
}

// As an example, we're going to write data manually into the first frame based on a buffer (i.e. raw data)
b, err := os.ReadFile(rawBufferPath)
if err != nil {
log.Fatal(fmt.Errorf("main: reading %s failed: %w", rawBufferPath, err))
}
if err := f1.Data().SetBytes(b, align); err != nil {
log.Fatal(fmt.Errorf("main: setting frame's data based on bytes failed: %w", err))
}

// As an example, we're going to write data manually into the second frame based on a Go image
fl1, err := os.Open(pngPath)
if err != nil {
log.Fatal(fmt.Errorf("main: opening %s failed: %w", pngPath, err))
}
defer fl1.Close()
i1, err := png.Decode(fl1)
if err != nil {
log.Fatal(fmt.Errorf("main: decoding %s failed: %w", pngPath, err))
}
if err := f2.Data().FromImage(i1); err != nil {
log.Fatal(fmt.Errorf("main: setting frame's data based on Go image failed: %w", err))
}

// This is the place where you do stuff with the frames

// As an example, we're going to read the first frame's data as a buffer (i.e. raw data)
if _, err = f1.Data().Bytes(align); err != nil {
log.Fatal(fmt.Errorf("main: getting frame's data as bytes failed: %w", err))
}

// As an example, we're going to read the second frame's data as a Go image
// For that we first need to guess the Go image format based on the frame's attributes before providing
// it to .ToImage(). You may not need this and can provide your own image.Image to .ToImage()
i2, err := f2.Data().GuessImageFormat()
if err != nil {
log.Fatal(fmt.Errorf("main: guessing image format failed: %w", err))
}
if err := f2.Data().ToImage(i2); err != nil {
log.Fatal(fmt.Errorf("main: getting frame's data as Go image failed: %w", err))
}

// Success
log.Println("success")
}
12 changes: 10 additions & 2 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ func (f *Frame) SetKeyFrame(k bool) {
}

func (f *Frame) ImageBufferSize(align int) (int, error) {
ret := C.av_image_get_buffer_size((C.enum_AVSampleFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
ret := C.av_image_get_buffer_size((C.enum_AVPixelFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
if err := newError(ret); err != nil {
return 0, err
}
return int(ret), nil
}

func (f *Frame) ImageCopyToBuffer(b []byte, align int) (int, error) {
ret := C.av_image_copy_to_buffer((*C.uint8_t)(unsafe.Pointer(&b[0])), C.int(len(b)), &f.c.data[0], &f.c.linesize[0], (C.enum_AVSampleFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
ret := C.av_image_copy_to_buffer((*C.uint8_t)(unsafe.Pointer(&b[0])), C.int(len(b)), &f.c.data[0], &f.c.linesize[0], (C.enum_AVPixelFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
if err := newError(ret); err != nil {
return 0, err
}
Expand Down Expand Up @@ -224,3 +224,11 @@ func (f *Frame) MoveRef(src *Frame) {
func (f *Frame) UnsafePointer() unsafe.Pointer {
return unsafe.Pointer(f.c)
}

func (f *Frame) IsWritable() bool {
return C.av_frame_is_writable(f.c) > 0
}

func (f *Frame) MakeWritable() error {
return newError(C.av_frame_make_writable(f.c))
}
Loading

0 comments on commit a5fe16a

Please sign in to comment.