mirror of
https://github.com/asticode/go-astiav.git
synced 2025-10-06 00:26:52 +08:00
Added frame data SetBytes() and FromImage()
This commit is contained in:
@@ -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)
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
@@ -107,7 +106,6 @@ func main() {
|
||||
}
|
||||
|
||||
// Loop through packets
|
||||
var i image.Image
|
||||
for {
|
||||
// Read frame
|
||||
if err := inputFormatContext.ReadFrame(pkt); err != nil {
|
||||
@@ -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.Printf("new %s frame: stream %d - pts: %d", s.inputStream.CodecParameters().MediaType(), pkt.StreamIndex(), f.Pts())
|
||||
}
|
||||
}
|
||||
|
||||
|
108
examples/frame_data_manipulating/main.go
Normal file
108
examples/frame_data_manipulating/main.go
Normal file
@@ -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
frame.go
12
frame.go
@@ -90,7 +90,7 @@ 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
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func (f *Frame) ImageBufferSize(align int) (int, error) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
176
frame_data.go
176
frame_data.go
@@ -1,6 +1,7 @@
|
||||
package astiav
|
||||
|
||||
//#include <libavutil/imgutils.h>
|
||||
//#include <stdlib.h>
|
||||
//#include "macros.h"
|
||||
import "C"
|
||||
import (
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type FrameData struct {
|
||||
@@ -16,9 +18,10 @@ type FrameData struct {
|
||||
|
||||
type frameDataFramer interface {
|
||||
bytes(align int) ([]byte, error)
|
||||
copyPlanes(ps []frameDataPlane) error
|
||||
height() int
|
||||
pixelFormat() PixelFormat
|
||||
planes() ([]frameDataPlane, error)
|
||||
planes(b []byte, align int) ([]frameDataPlane, error)
|
||||
width() int
|
||||
}
|
||||
|
||||
@@ -35,6 +38,21 @@ func (d *FrameData) Bytes(align int) ([]byte, error) {
|
||||
return d.f.bytes(align)
|
||||
}
|
||||
|
||||
// It's the developer's responsibility to handle frame's writability
|
||||
func (d *FrameData) SetBytes(b []byte, align int) error {
|
||||
// Get planes
|
||||
planes, err := d.f.planes(b, align)
|
||||
if err != nil {
|
||||
return fmt.Errorf("astiav: getting planes failed: %w", err)
|
||||
}
|
||||
|
||||
// Copy planes
|
||||
if err := d.f.copyPlanes(planes); err != nil {
|
||||
return fmt.Errorf("astiav: copying planes failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Always returns non-premultiplied formats when dealing with alpha channels, however this might not
|
||||
// always be accurate. In this case, use your own format in .ToImage()
|
||||
func (d *FrameData) GuessImageFormat() (image.Image, error) {
|
||||
@@ -118,41 +136,113 @@ func (d *FrameData) toImageYCbCrA(y, cb, cr, a *[]uint8, yStride, cStride, aStri
|
||||
}
|
||||
|
||||
func (d *FrameData) ToImage(dst image.Image) error {
|
||||
// Get bytes
|
||||
// Using bytesFromC on f.c.data caused random segfaults
|
||||
const align = 1
|
||||
b, err := d.f.bytes(align)
|
||||
if err != nil {
|
||||
return fmt.Errorf("astiav: getting bytes failed: %w", err)
|
||||
}
|
||||
|
||||
// Get planes
|
||||
planes, err := d.f.planes()
|
||||
planes, err := d.f.planes(b, align)
|
||||
if err != nil {
|
||||
return fmt.Errorf("astiav: getting planes failed: %w", err)
|
||||
}
|
||||
|
||||
// Update image
|
||||
if v, ok := dst.(*image.Alpha); ok {
|
||||
switch v := dst.(type) {
|
||||
case *image.Alpha:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.Alpha16); ok {
|
||||
case *image.Alpha16:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.CMYK); ok {
|
||||
case *image.CMYK:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.Gray); ok {
|
||||
case *image.Gray:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.Gray16); ok {
|
||||
case *image.Gray16:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.NRGBA); ok {
|
||||
case *image.NRGBA:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.NRGBA64); ok {
|
||||
case *image.NRGBA64:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.NYCbCrA); ok {
|
||||
case *image.NYCbCrA:
|
||||
d.toImageYCbCrA(&v.Y, &v.Cb, &v.Cr, &v.A, &v.YStride, &v.CStride, &v.AStride, &v.SubsampleRatio, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.RGBA); ok {
|
||||
case *image.RGBA:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.RGBA64); ok {
|
||||
case *image.RGBA64:
|
||||
d.toImagePix(&v.Pix, &v.Stride, &v.Rect, planes)
|
||||
} else if v, ok := dst.(*image.YCbCr); ok {
|
||||
case *image.YCbCr:
|
||||
d.toImageYCbCr(&v.Y, &v.Cb, &v.Cr, &v.YStride, &v.CStride, &v.SubsampleRatio, &v.Rect, planes)
|
||||
} else {
|
||||
default:
|
||||
return errors.New("astiav: image format is not handled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FrameData) fromImagePix(pix []uint8, stride int) error {
|
||||
// Copy planes
|
||||
if err := d.f.copyPlanes([]frameDataPlane{{bytes: pix, linesize: stride}}); err != nil {
|
||||
return fmt.Errorf("astiav: copying planes failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FrameData) fromImageYCbCr(y, cb, cr []uint8, yStride, cStride int) error {
|
||||
// Copy planes
|
||||
if err := d.f.copyPlanes([]frameDataPlane{
|
||||
{bytes: y, linesize: yStride},
|
||||
{bytes: cb, linesize: cStride},
|
||||
{bytes: cr, linesize: cStride},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("astiav: copying planes failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FrameData) fromImageYCbCrA(y, cb, cr, a []uint8, yStride, cStride, aStride int) error {
|
||||
// Copy planes
|
||||
if err := d.f.copyPlanes([]frameDataPlane{
|
||||
{bytes: y, linesize: yStride},
|
||||
{bytes: cb, linesize: cStride},
|
||||
{bytes: cr, linesize: cStride},
|
||||
{bytes: a, linesize: aStride},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("astiav: copying planes failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// It's the developer's responsibility to handle frame's writability
|
||||
func (d *FrameData) FromImage(src image.Image) error {
|
||||
// Copy planes
|
||||
switch v := src.(type) {
|
||||
case *image.Alpha:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.Alpha16:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.CMYK:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.Gray:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.Gray16:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.NRGBA:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.NRGBA64:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.NYCbCrA:
|
||||
return d.fromImageYCbCrA(v.Y, v.Cb, v.Cr, v.A, v.YStride, v.CStride, v.AStride)
|
||||
case *image.RGBA:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.RGBA64:
|
||||
return d.fromImagePix(v.Pix, v.Stride)
|
||||
case *image.YCbCr:
|
||||
return d.fromImageYCbCr(v.Y, v.Cb, v.Cr, v.YStride, v.CStride)
|
||||
}
|
||||
return errors.New("astiav: image format is not handled")
|
||||
}
|
||||
|
||||
var _ frameDataFramer = (*frameDataFrame)(nil)
|
||||
|
||||
type frameDataFrame struct {
|
||||
@@ -190,6 +280,31 @@ func (f *frameDataFrame) bytes(align int) ([]byte, error) {
|
||||
return nil, errors.New("astiav: frame type not implemented")
|
||||
}
|
||||
|
||||
func (f *frameDataFrame) copyPlanes(ps []frameDataPlane) (err error) {
|
||||
switch {
|
||||
// Video
|
||||
case f.height() > 0 && f.width() > 0:
|
||||
// Loop through planes
|
||||
var cdata [8]*C.uint8_t
|
||||
var clinesizes [8]C.int
|
||||
for i, p := range ps {
|
||||
// Convert data
|
||||
if len(p.bytes) > 0 {
|
||||
cdata[i] = (*C.uint8_t)(C.CBytes(p.bytes))
|
||||
defer C.free(unsafe.Pointer(cdata[i]))
|
||||
}
|
||||
|
||||
// Convert linesize
|
||||
clinesizes[i] = C.int(p.linesize)
|
||||
}
|
||||
|
||||
// Copy image
|
||||
C.av_image_copy(&f.f.c.data[0], &f.f.c.linesize[0], &cdata[0], &clinesizes[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.width, f.f.c.height)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *frameDataFrame) height() int {
|
||||
return f.f.Height()
|
||||
}
|
||||
@@ -198,44 +313,36 @@ func (f *frameDataFrame) pixelFormat() PixelFormat {
|
||||
return f.f.PixelFormat()
|
||||
}
|
||||
|
||||
// Using bytesFromC on f.c.data caused random segfaults
|
||||
func (f *frameDataFrame) planes() ([]frameDataPlane, error) {
|
||||
// Get bytes
|
||||
const align = 1
|
||||
b, err := f.bytes(align)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("astiav: getting bytes failed: %w", err)
|
||||
}
|
||||
|
||||
func (f *frameDataFrame) planes(b []byte, align int) ([]frameDataPlane, error) {
|
||||
switch {
|
||||
// Video
|
||||
case f.height() > 0 && f.width() > 0:
|
||||
// Below is mostly inspired by https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/libavutil/imgutils.c#L466
|
||||
|
||||
// Get linesize
|
||||
var linesize [4]C.int
|
||||
if err := newError(C.av_image_fill_linesizes(&linesize[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.width)); err != nil {
|
||||
var linesizes [4]C.int
|
||||
if err := newError(C.av_image_fill_linesizes(&linesizes[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.width)); err != nil {
|
||||
return nil, fmt.Errorf("astiav: getting linesize failed: %w", err)
|
||||
}
|
||||
|
||||
// Align linesize
|
||||
var alignedLinesize [4]C.ptrdiff_t
|
||||
var alignedLinesizes [4]C.ptrdiff_t
|
||||
for i := 0; i < 4; i++ {
|
||||
alignedLinesize[i] = C.astiavFFAlign(linesize[i], C.int(align))
|
||||
alignedLinesizes[i] = C.astiavFFAlign(linesizes[i], C.int(align))
|
||||
}
|
||||
|
||||
// Get plane sizes
|
||||
var planeSizes [4]C.size_t
|
||||
if err := newError(C.av_image_fill_plane_sizes(&planeSizes[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.height, &alignedLinesize[0])); err != nil {
|
||||
if err := newError(C.av_image_fill_plane_sizes(&planeSizes[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.height, &alignedLinesizes[0])); err != nil {
|
||||
return nil, fmt.Errorf("astiav: getting plane sizes failed: %w", err)
|
||||
}
|
||||
|
||||
// Loop through plane sizes
|
||||
// Loop through planes
|
||||
var ps []frameDataPlane
|
||||
start := 0
|
||||
for idx, planeSize := range planeSizes {
|
||||
for i := range planeSizes {
|
||||
// Get end
|
||||
end := start + int(planeSize)
|
||||
end := start + int(planeSizes[i])
|
||||
if len(b) < end {
|
||||
return nil, fmt.Errorf("astiav: buffer length %d is invalid for [%d:%d]", len(b), start, end)
|
||||
}
|
||||
@@ -243,16 +350,17 @@ func (f *frameDataFrame) planes() ([]frameDataPlane, error) {
|
||||
// Append plane
|
||||
ps = append(ps, frameDataPlane{
|
||||
bytes: b[start:end],
|
||||
linesize: int(alignedLinesize[idx]),
|
||||
linesize: int(linesizes[i]),
|
||||
})
|
||||
|
||||
// Update start
|
||||
start += int(planeSize)
|
||||
start = end
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("astiav: frame type not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frameDataFrame) width() int {
|
||||
return f.f.Width()
|
||||
|
@@ -10,17 +10,23 @@ import (
|
||||
)
|
||||
|
||||
type mockedFrameDataFrame struct {
|
||||
copiedPlanes []frameDataPlane
|
||||
h int
|
||||
imageBytes []byte
|
||||
onBytes func(align int) ([]byte, error)
|
||||
onPlanes func(b []byte, align int) ([]frameDataPlane, error)
|
||||
pf PixelFormat
|
||||
planes_ []frameDataPlane
|
||||
w int
|
||||
}
|
||||
|
||||
var _ frameDataFramer = (*mockedFrameDataFrame)(nil)
|
||||
|
||||
func (f *mockedFrameDataFrame) bytes(align int) ([]byte, error) {
|
||||
return f.imageBytes, nil
|
||||
return f.onBytes(align)
|
||||
}
|
||||
|
||||
func (f *mockedFrameDataFrame) copyPlanes(ps []frameDataPlane) error {
|
||||
f.copiedPlanes = ps
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *mockedFrameDataFrame) height() int {
|
||||
@@ -31,8 +37,8 @@ func (f *mockedFrameDataFrame) pixelFormat() PixelFormat {
|
||||
return f.pf
|
||||
}
|
||||
|
||||
func (f *mockedFrameDataFrame) planes() ([]frameDataPlane, error) {
|
||||
return f.planes_, nil
|
||||
func (f *mockedFrameDataFrame) planes(b []byte, align int) ([]frameDataPlane, error) {
|
||||
return f.onPlanes(b, align)
|
||||
}
|
||||
|
||||
func (f *mockedFrameDataFrame) width() int {
|
||||
@@ -114,15 +120,15 @@ func TestFrameDataInternal(t *testing.T) {
|
||||
}
|
||||
|
||||
fdf.h = 1
|
||||
fdf.imageBytes = []byte{0, 1, 2, 3}
|
||||
b1 := []byte{0, 1, 2, 3}
|
||||
fdf.onBytes = func(align int) ([]byte, error) { return b1, nil }
|
||||
fdf.w = 2
|
||||
b, err := fd.Bytes(0)
|
||||
b2, err := fd.Bytes(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fdf.imageBytes, b)
|
||||
require.Equal(t, b1, b2)
|
||||
|
||||
for _, v := range []struct {
|
||||
e image.Image
|
||||
err bool
|
||||
i image.Image
|
||||
pixelFormat PixelFormat
|
||||
planes []frameDataPlane
|
||||
@@ -326,13 +332,133 @@ func TestFrameDataInternal(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
fdf.pf = v.pixelFormat
|
||||
fdf.planes_ = v.planes
|
||||
err = fd.ToImage(v.i)
|
||||
if v.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
fdf.onPlanes = func(b []byte, align int) ([]frameDataPlane, error) { return v.planes, nil }
|
||||
require.NoError(t, fd.ToImage(v.i))
|
||||
require.Equal(t, v.e, v.i)
|
||||
}
|
||||
|
||||
b1 = []byte{1, 2, 3, 4}
|
||||
fdf.onPlanes = func(b []byte, align int) ([]frameDataPlane, error) {
|
||||
return []frameDataPlane{
|
||||
{
|
||||
bytes: b1[:2],
|
||||
linesize: 1,
|
||||
},
|
||||
{
|
||||
bytes: b1[2:],
|
||||
linesize: 2,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
require.NoError(t, fd.SetBytes(b1, 0))
|
||||
require.Equal(t, []frameDataPlane{
|
||||
{bytes: b1[:2], linesize: 1},
|
||||
{bytes: b1[2:], linesize: 2},
|
||||
}, fdf.copiedPlanes)
|
||||
|
||||
for _, v := range []struct {
|
||||
expectedCopiedPlanes []frameDataPlane
|
||||
i image.Image
|
||||
}{
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.Alpha{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.Alpha16{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.CMYK{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.Gray{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.Gray16{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.NRGBA{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.NRGBA64{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{
|
||||
{bytes: []byte{0, 1}, linesize: 1},
|
||||
{bytes: []byte{2, 3}, linesize: 2},
|
||||
{bytes: []byte{4, 5}, linesize: 2},
|
||||
{bytes: []byte{6, 7}, linesize: 4},
|
||||
},
|
||||
i: &image.NYCbCrA{
|
||||
A: []byte{6, 7},
|
||||
AStride: 4,
|
||||
YCbCr: image.YCbCr{
|
||||
Y: []byte{0, 1},
|
||||
Cb: []byte{2, 3},
|
||||
Cr: []byte{4, 5},
|
||||
YStride: 1,
|
||||
CStride: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.RGBA{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{{bytes: []byte{0, 1, 2, 3}, linesize: 1}},
|
||||
i: &image.RGBA64{
|
||||
Pix: []byte{0, 1, 2, 3},
|
||||
Stride: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedCopiedPlanes: []frameDataPlane{
|
||||
{bytes: []byte{0, 1}, linesize: 1},
|
||||
{bytes: []byte{2, 3}, linesize: 2},
|
||||
{bytes: []byte{4, 5}, linesize: 2},
|
||||
},
|
||||
i: &image.YCbCr{
|
||||
Y: []byte{0, 1},
|
||||
Cb: []byte{2, 3},
|
||||
Cr: []byte{4, 5},
|
||||
YStride: 1,
|
||||
CStride: 2,
|
||||
},
|
||||
},
|
||||
} {
|
||||
require.NoError(t, fd.FromImage(v.i))
|
||||
require.Equal(t, v.expectedCopiedPlanes, fdf.copiedPlanes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,24 +476,47 @@ func TestFrameData(t *testing.T) {
|
||||
name: "video-yuv420p",
|
||||
},
|
||||
} {
|
||||
f, err := globalHelper.inputLastFrame(v.name+"."+v.ext, MediaTypeVideo)
|
||||
f1, err := globalHelper.inputLastFrame(v.name+"."+v.ext, MediaTypeVideo)
|
||||
require.NoError(t, err)
|
||||
fd := f.Data()
|
||||
fd1 := f1.Data()
|
||||
|
||||
b1, err := fd.Bytes(1)
|
||||
b1, err := fd1.Bytes(1)
|
||||
require.NoError(t, err)
|
||||
b2 := []byte(fmt.Sprintf("%+v", b1))
|
||||
b3, err := os.ReadFile("testdata/" + v.name + "-bytes")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, b2, b3)
|
||||
require.Equal(t, b3, b2)
|
||||
|
||||
i1, err := fd.GuessImageFormat()
|
||||
i1, err := fd1.GuessImageFormat()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fd.ToImage(i1))
|
||||
require.NoError(t, fd1.ToImage(i1))
|
||||
b4 := []byte(fmt.Sprintf("%+v", i1))
|
||||
b5, err := os.ReadFile("testdata/" + v.name + "-struct")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, b4, b5)
|
||||
require.Equal(t, b5, b4)
|
||||
|
||||
f2 := AllocFrame()
|
||||
defer f2.Free()
|
||||
f2.SetHeight(f1.Height())
|
||||
f2.SetPixelFormat(f1.PixelFormat())
|
||||
f2.SetWidth(f1.Width())
|
||||
const align = 1
|
||||
require.NoError(t, f2.AllocBuffer(align))
|
||||
require.NoError(t, f2.AllocImage(align))
|
||||
fd2 := f2.Data()
|
||||
|
||||
require.NoError(t, fd2.FromImage(i1))
|
||||
b6, err := fd2.Bytes(align)
|
||||
require.NoError(t, err)
|
||||
b7 := []byte(fmt.Sprintf("%+v", b6))
|
||||
require.Equal(t, b3, b7)
|
||||
|
||||
require.NoError(t, f2.ImageFillBlack())
|
||||
require.NoError(t, fd2.SetBytes(b1, align))
|
||||
b1[0] -= 1
|
||||
b8, err := fd2.Bytes(align)
|
||||
require.NoError(t, err)
|
||||
b9 := []byte(fmt.Sprintf("%+v", b8))
|
||||
require.Equal(t, b3, b9)
|
||||
}
|
||||
}
|
||||
|
@@ -115,4 +115,10 @@ func TestFrame(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 12, n)
|
||||
require.Equal(t, []byte{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x80, 0x80, 0x80, 0x80}, b)
|
||||
|
||||
require.True(t, f6.IsWritable())
|
||||
require.NoError(t, f5.Ref(f6))
|
||||
require.False(t, f6.IsWritable())
|
||||
require.NoError(t, f6.MakeWritable())
|
||||
require.True(t, f6.IsWritable())
|
||||
}
|
||||
|
BIN
testdata/image-rgba.rgba
vendored
Normal file
BIN
testdata/image-rgba.rgba
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user