diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md new file mode 100644 index 0000000..98c047b --- /dev/null +++ b/BREAKING_CHANGES.md @@ -0,0 +1,4 @@ +# v0.24.0 + +- use `FilterGraph`.`NewBuffersinkFilterContext` and `FilterGraph`.`NewBuffersrcFilterContext` instead of `FilterGraph`.`NewFilterContext` when creating `buffersink` and `buffersrc` filter contexts and use `BuffersinkFilterContext`.`GetFrame` and `BuffersrcFilterContext`.`AddFrame` to manipulate them. Use `BuffersinkFilterContext`.`FilterContext` and `BuffersrcFilterContext`.`FilterContext` in `FilterInOut`.`SetFilterContext`. +- `FilterLink` has been removed and methods like `BuffersinkFilterContext`.`ChannelLayout` have been added instead \ No newline at end of file diff --git a/README.md b/README.md index 7f8c98c..6b08601 100644 --- a/README.md +++ b/README.md @@ -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 manipulation|[see](examples/frame_data_manipulation/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) |Hardware Encoding|[see](examples/hardware_encoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/vaapi_encode.c) |Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/remuxing.c) @@ -57,4 +58,8 @@ export PKG_CONFIG_PATH="{{ path to your working directory }}/tmp/n7.0/lib/pkgcon # Why astiav? -After maintaining for several years the most starred [fork](https://github.com/asticode/goav) of [goav](https://github.com/giorgisio/goav), I've decided to write from scratch my own C bindings to fix most of the problems I still encountered using `goav`. \ No newline at end of file +After maintaining for several years the most starred [fork](https://github.com/asticode/goav) of [goav](https://github.com/giorgisio/goav), I've decided to write from scratch my own C bindings to fix most of the problems I still encountered using `goav`. + +# Breaking changes + +You can see the list of breaking changes [here](BREAKING_CHANGES.md). \ No newline at end of file diff --git a/astiav_test.go b/astiav_test.go index d7673f3..64f7cb3 100644 --- a/astiav_test.go +++ b/astiav_test.go @@ -50,7 +50,7 @@ type helperInput struct { lastFrame *Frame } -func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error) { +func (h *helper) inputFormatContext(name string, ifmt *InputFormat) (fc *FormatContext, err error) { h.m.Lock() i, ok := h.inputs[name] if ok && i.formatContext != nil { @@ -65,7 +65,7 @@ func (h *helper) inputFormatContext(name string) (fc *FormatContext, err error) } h.closer.Add(fc.Free) - if err = fc.OpenInput("testdata/"+name, nil, nil); err != nil { + if err = fc.OpenInput("testdata/"+name, ifmt, nil); err != nil { err = fmt.Errorf("astiav_test: opening input failed: %w", err) return } @@ -95,7 +95,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) { h.m.Unlock() var fc *FormatContext - if fc, err = h.inputFormatContext(name); err != nil { + if fc, err = h.inputFormatContext(name, nil); err != nil { err = fmt.Errorf("astiav_test: getting input format context failed") return } @@ -118,7 +118,7 @@ func (h *helper) inputFirstPacket(name string) (pkt *Packet, err error) { return } -func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err error) { +func (h *helper) inputLastFrame(name string, mediaType MediaType, ifmt *InputFormat) (f *Frame, err error) { h.m.Lock() i, ok := h.inputs[name] if ok && i.lastFrame != nil { @@ -128,7 +128,7 @@ func (h *helper) inputLastFrame(name string, mediaType MediaType) (f *Frame, err h.m.Unlock() var fc *FormatContext - if fc, err = h.inputFormatContext(name); err != nil { + if fc, err = h.inputFormatContext(name, ifmt); err != nil { err = fmt.Errorf("astiav_test: getting input format context failed: %w", err) return } diff --git a/bit_stream_filter_context.go b/bit_stream_filter_context.go index 39f1d4e..eaa6c87 100644 --- a/bit_stream_filter_context.go +++ b/bit_stream_filter_context.go @@ -60,8 +60,17 @@ func (bsfc *BitStreamFilterContext) ReceivePacket(p *Packet) error { } func (bsfc *BitStreamFilterContext) Free() { - classers.del(bsfc) - C.av_bsf_free(&bsfc.c) + if bsfc.c != nil { + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(bsfc) + C.av_bsf_free(&bsfc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } + } } func (bsfc *BitStreamFilterContext) InputTimeBase() Rational { diff --git a/class.go b/class.go index 48d81a2..7bc4594 100644 --- a/class.go +++ b/class.go @@ -52,6 +52,8 @@ type Classer interface { Class() *Class } +var _ Classer = (*UnknownClasser)(nil) + type UnknownClasser struct { c *Class } @@ -64,6 +66,24 @@ func (c *UnknownClasser) Class() *Class { return c.c } +var _ Classer = (*ClonedClasser)(nil) + +type ClonedClasser struct { + c *Class +} + +func newClonedClasser(c Classer) *ClonedClasser { + cl := c.Class() + if cl == nil { + return nil + } + return &ClonedClasser{c: newClassFromC(cl.ptr)} +} + +func (c *ClonedClasser) Class() *Class { + return c.c +} + var classers = newClasserPool() type classerPool struct { diff --git a/class_test.go b/class_test.go index 72ae915..2187085 100644 --- a/class_test.go +++ b/class_test.go @@ -28,36 +28,48 @@ func TestClass(t *testing.T) { func TestClassers(t *testing.T) { cl := len(classers.p) - f := AllocFilterGraph() + f1 := AllocFilterGraph() + f2 := AllocFilterGraph() c := FindDecoder(CodecIDMjpeg) require.NotNil(t, c) + bf := FindBitStreamFilterByName("null") + require.NotNil(t, bf) + bfc, err := AllocBitStreamFilterContext(bf) + require.NoError(t, err) cc := AllocCodecContext(c) require.NotNil(t, cc) bufferSink := FindFilterByName("buffersink") require.NotNil(t, bufferSink) - fc, err := f.NewFilterContext(bufferSink, "filter_out", nil) + fc1, err := f1.NewFilterContext(bufferSink, "filter_out", nil) + require.NoError(t, err) + _, err = f2.NewFilterContext(bufferSink, "filter_out", nil) require.NoError(t, err) fmc1 := AllocFormatContext() fmc2 := AllocFormatContext() require.NoError(t, fmc2.OpenInput("testdata/video.mp4", nil, nil)) path := filepath.Join(t.TempDir(), "iocontext.txt") - ic, err := OpenIOContext(path, NewIOContextFlags(IOContextFlagWrite)) + ic1, err := OpenIOContext(path, NewIOContextFlags(IOContextFlagWrite)) require.NoError(t, err) defer os.RemoveAll(path) + ic2, err := AllocIOContext(1, true, nil, nil, nil) + require.NoError(t, err) ssc, err := CreateSoftwareScaleContext(1, 1, PixelFormatRgba, 2, 2, PixelFormatRgba, NewSoftwareScaleContextFlags()) require.NoError(t, err) - require.Equal(t, cl+8, len(classers.p)) - v, ok := classers.get(unsafe.Pointer(f.c)) + require.Equal(t, cl+12, len(classers.p)) + v, ok := classers.get(unsafe.Pointer(f1.c)) require.True(t, ok) - require.Equal(t, f, v) + require.Equal(t, f1, v) + bfc.Free() cc.Free() - fc.Free() - f.Free() + fc1.Free() + f1.Free() + f2.Free() fmc1.Free() fmc2.CloseInput() - require.NoError(t, ic.Close()) + require.NoError(t, ic1.Close()) + ic2.Free() ssc.Free() require.Equal(t, cl, len(classers.p)) } diff --git a/codec_context.go b/codec_context.go index 8ab8dfa..9044944 100644 --- a/codec_context.go +++ b/codec_context.go @@ -43,8 +43,17 @@ func (cc *CodecContext) Free() { C.av_buffer_unref(&cc.hfc.c) cc.hfc = nil } - classers.del(cc) - C.avcodec_free_context(&cc.c) + if cc.c != nil { + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(cc) + C.avcodec_free_context(&cc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } + } } func (cc *CodecContext) String() string { diff --git a/codec_context_test.go b/codec_context_test.go index 714c55f..7f7601b 100644 --- a/codec_context_test.go +++ b/codec_context_test.go @@ -7,7 +7,7 @@ import ( ) func TestCodecContext(t *testing.T) { - fc, err := globalHelper.inputFormatContext("video.mp4") + fc, err := globalHelper.inputFormatContext("video.mp4", nil) require.NoError(t, err) ss := fc.Streams() require.Len(t, ss, 2) diff --git a/codec_parameters_test.go b/codec_parameters_test.go index e5a2358..caaff25 100644 --- a/codec_parameters_test.go +++ b/codec_parameters_test.go @@ -7,7 +7,7 @@ import ( ) func TestCodecParameters(t *testing.T) { - fc, err := globalHelper.inputFormatContext("video.mp4") + fc, err := globalHelper.inputFormatContext("video.mp4", nil) require.NoError(t, err) ss := fc.Streams() require.Len(t, ss, 2) diff --git a/examples/demuxing_decoding/main.go b/examples/demuxing_decoding/main.go index c2b941d..5fce9e0 100644 --- a/examples/demuxing_decoding/main.go +++ b/examples/demuxing_decoding/main.go @@ -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 + log.Printf("new %s frame: stream %d - pts: %d", s.inputStream.CodecParameters().MediaType(), pkt.StreamIndex(), f.Pts()) } } diff --git a/examples/filtering/main.go b/examples/filtering/main.go index 9090fc0..993bd7f 100644 --- a/examples/filtering/main.go +++ b/examples/filtering/main.go @@ -23,8 +23,8 @@ var ( ) type stream struct { - buffersinkContext *astiav.FilterContext - buffersrcContext *astiav.FilterContext + buffersinkContext *astiav.BuffersinkFilterContext + buffersrcContext *astiav.BuffersrcFilterContext decCodec *astiav.Codec decCodecContext *astiav.CodecContext decFrame *astiav.Frame @@ -232,7 +232,7 @@ func initFilter() (err error) { } // Create filter contexts - if s.buffersrcContext, err = s.filterGraph.NewFilterContext(buffersrc, "in", astiav.FilterArgs{ + if s.buffersrcContext, err = s.filterGraph.NewBuffersrcFilterContext(buffersrc, "in", astiav.FilterArgs{ "pix_fmt": strconv.Itoa(int(s.decCodecContext.PixelFormat())), "pixel_aspect": s.decCodecContext.SampleAspectRatio().String(), "time_base": s.inputStream.TimeBase().String(), @@ -241,20 +241,20 @@ func initFilter() (err error) { err = fmt.Errorf("main: creating buffersrc context failed: %w", err) return } - if s.buffersinkContext, err = s.filterGraph.NewFilterContext(buffersink, "in", nil); err != nil { + if s.buffersinkContext, err = s.filterGraph.NewBuffersinkFilterContext(buffersink, "in", nil); err != nil { err = fmt.Errorf("main: creating buffersink context failed: %w", err) return } // Update outputs outputs.SetName("in") - outputs.SetFilterContext(s.buffersrcContext) + outputs.SetFilterContext(s.buffersrcContext.FilterContext()) outputs.SetPadIdx(0) outputs.SetNext(nil) // Update inputs inputs.SetName("out") - inputs.SetFilterContext(s.buffersinkContext) + inputs.SetFilterContext(s.buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -278,7 +278,7 @@ func initFilter() (err error) { func filterFrame(f *astiav.Frame, s *stream) (err error) { // Add frame - if err = s.buffersrcContext.BuffersrcAddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { + if err = s.buffersrcContext.AddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { err = fmt.Errorf("main: adding frame failed: %w", err) return } @@ -289,7 +289,7 @@ func filterFrame(f *astiav.Frame, s *stream) (err error) { s.filterFrame.Unref() // Get frame - if err = s.buffersinkContext.BuffersinkGetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { + if err = s.buffersinkContext.GetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { err = nil break diff --git a/examples/frame_data_manipulation/main.go b/examples/frame_data_manipulation/main.go new file mode 100644 index 0000000..e3778c3 --- /dev/null +++ b/examples/frame_data_manipulation/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + "image" + "log" + "strings" + + "github.com/asticode/go-astiav" +) + +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) + }) + + /* + + In this first part we're going to manipulate an audio frame + + */ + + // Alloc frame + audioFrame := astiav.AllocFrame() + defer audioFrame.Free() + + // To write data manually into a frame, proper attributes need to be set and allocated + audioFrame.SetChannelLayout(astiav.ChannelLayoutStereo) + audioFrame.SetNbSamples(960) + audioFrame.SetSampleFormat(astiav.SampleFormatFlt) + audioFrame.SetSampleRate(48000) + + // Alloc buffer + align := 0 + if err := audioFrame.AllocBuffer(align); err != nil { + log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err)) + } + + // Alloc samples + if err := audioFrame.AllocSamples(align); err != nil { + log.Fatal(fmt.Errorf("main: allocating image failed: %w", err)) + } + + // When writing data manually into a frame, you need to make sure the frame is writable + if err := audioFrame.MakeWritable(); err != nil { + log.Fatal(fmt.Errorf("main: making frame writable failed: %w", err)) + } + + // Let's say b1 contains an actual audio buffer, we can update the audio frame's data based on the buffer + var b1 []byte + if err := audioFrame.Data().SetBytes(b1, align); err != nil { + log.Fatal(fmt.Errorf("main: setting frame's data based from bytes failed: %w", err)) + } + + // We can also retrieve the audio frame's data as buffer + if _, err := audioFrame.Data().Bytes(align); err != nil { + log.Fatal(fmt.Errorf("main: getting frame's data as bytes failed: %w", err)) + } + + /* + + In this second part we're going to manipulate a video frame + + */ + + // Alloc frame + videoFrame := astiav.AllocFrame() + defer videoFrame.Free() + + // To write data manually into a frame, proper attributes need to be set and allocated + videoFrame.SetHeight(256) + videoFrame.SetPixelFormat(astiav.PixelFormatRgba) + videoFrame.SetWidth(256) + + // Alloc buffer + align = 1 + if err := videoFrame.AllocBuffer(align); err != nil { + log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err)) + } + + // Alloc image + if err := videoFrame.AllocImage(align); err != nil { + log.Fatal(fmt.Errorf("main: allocating image failed: %w", err)) + } + + // When writing data manually into a frame, you need to make sure the frame is writable + if err := videoFrame.MakeWritable(); err != nil { + log.Fatal(fmt.Errorf("main: making frame writable failed: %w", err)) + } + + // Let's say b2 contains an actual video buffer, we can update the video frame's data based on the buffer + var b2 []byte + if err := videoFrame.Data().SetBytes(b2, align); err != nil { + log.Fatal(fmt.Errorf("main: setting frame's data based from bytes failed: %w", err)) + } + + // We can also retrieve the video frame's data as buffer + if _, err := videoFrame.Data().Bytes(align); err != nil { + log.Fatal(fmt.Errorf("main: getting frame's data as bytes failed: %w", err)) + } + + // Let's say i1 is an actual Go image.Image, we can update the video frame's data based on the image + var i1 image.Image + if err := videoFrame.Data().FromImage(i1); err != nil { + log.Fatal(fmt.Errorf("main: setting frame's data based on Go image failed: %w", err)) + } + + // We can also retrieve the video 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 := videoFrame.Data().GuessImageFormat() + if err != nil { + log.Fatal(fmt.Errorf("main: guessing image format failed: %w", err)) + } + if err := videoFrame.Data().ToImage(i2); err != nil { + log.Fatal(fmt.Errorf("main: getting frame's data as Go image failed: %w", err)) + } + + // Success + log.Println("success") +} diff --git a/examples/transcoding/main.go b/examples/transcoding/main.go index 6eb40e4..f1024ba 100644 --- a/examples/transcoding/main.go +++ b/examples/transcoding/main.go @@ -25,8 +25,8 @@ var ( ) type stream struct { - buffersinkContext *astiav.FilterContext - buffersrcContext *astiav.FilterContext + buffersinkContext *astiav.BuffersinkFilterContext + buffersrcContext *astiav.BuffersrcFilterContext decCodec *astiav.Codec decCodecContext *astiav.CodecContext decFrame *astiav.Frame @@ -395,24 +395,24 @@ func initFilters() (err error) { } // Create filter contexts - if s.buffersrcContext, err = s.filterGraph.NewFilterContext(buffersrc, "in", args); err != nil { + if s.buffersrcContext, err = s.filterGraph.NewBuffersrcFilterContext(buffersrc, "in", args); err != nil { err = fmt.Errorf("main: creating buffersrc context failed: %w", err) return } - if s.buffersinkContext, err = s.filterGraph.NewFilterContext(buffersink, "out", nil); err != nil { + if s.buffersinkContext, err = s.filterGraph.NewBuffersinkFilterContext(buffersink, "out", nil); err != nil { err = fmt.Errorf("main: creating buffersink context failed: %w", err) return } // Update outputs outputs.SetName("in") - outputs.SetFilterContext(s.buffersrcContext) + outputs.SetFilterContext(s.buffersrcContext.FilterContext()) outputs.SetPadIdx(0) outputs.SetNext(nil) // Update inputs inputs.SetName("out") - inputs.SetFilterContext(s.buffersinkContext) + inputs.SetFilterContext(s.buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -441,7 +441,7 @@ func initFilters() (err error) { func filterEncodeWriteFrame(f *astiav.Frame, s *stream) (err error) { // Add frame - if err = s.buffersrcContext.BuffersrcAddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { + if err = s.buffersrcContext.AddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { err = fmt.Errorf("main: adding frame failed: %w", err) return } @@ -452,7 +452,7 @@ func filterEncodeWriteFrame(f *astiav.Frame, s *stream) (err error) { s.filterFrame.Unref() // Get frame - if err = s.buffersinkContext.BuffersinkGetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { + if err = s.buffersinkContext.GetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil { if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { err = nil break diff --git a/filter_context.go b/filter_context.go index ed8cb8d..d91361b 100644 --- a/filter_context.go +++ b/filter_context.go @@ -6,7 +6,6 @@ package astiav //#include import "C" import ( - "math" "unsafe" ) @@ -27,50 +26,107 @@ func newFilterContext(c *C.AVFilterContext) *FilterContext { var _ Classer = (*FilterContext)(nil) func (fc *FilterContext) Free() { - classers.del(fc) + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(fc) C.avfilter_free(fc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } } -func (fc *FilterContext) BuffersrcAddFrame(f *Frame, fs BuffersrcFlags) error { - var cf *C.AVFrame - if f != nil { - cf = f.c - } - return newError(C.av_buffersrc_add_frame_flags(fc.c, cf, C.int(fs))) +func (fc *FilterContext) Class() *Class { + return newClassFromC(unsafe.Pointer(fc.c)) +} + +type BuffersinkFilterContext struct { + fc *FilterContext +} + +func newBuffersinkFilterContext(fc *FilterContext) *BuffersinkFilterContext { + return &BuffersinkFilterContext{fc: fc} +} + +func (bfc *BuffersinkFilterContext) ChannelLayout() ChannelLayout { + var cl C.AVChannelLayout + C.av_buffersink_get_ch_layout(bfc.fc.c, &cl) + return newChannelLayoutFromC(&cl) +} + +func (bfc *BuffersinkFilterContext) ColorRange() ColorRange { + return ColorRange(C.av_buffersink_get_color_range(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) ColorSpace() ColorSpace { + return ColorSpace(C.av_buffersink_get_colorspace(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) FilterContext() *FilterContext { + return bfc.fc +} + +func (bfc *BuffersinkFilterContext) FrameRate() Rational { + return newRationalFromC(C.av_buffersink_get_frame_rate(bfc.fc.c)) } -func (fc *FilterContext) BuffersinkGetFrame(f *Frame, fs BuffersinkFlags) error { +func (bfc *BuffersinkFilterContext) GetFrame(f *Frame, fs BuffersinkFlags) error { var cf *C.AVFrame if f != nil { cf = f.c } - return newError(C.av_buffersink_get_frame_flags(fc.c, cf, C.int(fs))) + return newError(C.av_buffersink_get_frame_flags(bfc.fc.c, cf, C.int(fs))) } -func (fc *FilterContext) Class() *Class { - return newClassFromC(unsafe.Pointer(fc.c)) +func (bfc *BuffersinkFilterContext) Height() int { + return int(C.av_buffersink_get_h(bfc.fc.c)) } -func (fc *FilterContext) NbInputs() int { - return int(fc.c.nb_inputs) +func (bfc *BuffersinkFilterContext) MediaType() MediaType { + return MediaType(C.av_buffersink_get_type(bfc.fc.c)) } -func (fc *FilterContext) NbOutputs() int { - return int(fc.c.nb_outputs) +func (bfc *BuffersinkFilterContext) PixelFormat() PixelFormat { + return PixelFormat(C.av_buffersink_get_format(bfc.fc.c)) } -func (fc *FilterContext) Inputs() (ls []*FilterLink) { - lcs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterLink)(nil))](*C.AVFilterLink))(unsafe.Pointer(fc.c.inputs)) - for i := 0; i < fc.NbInputs(); i++ { - ls = append(ls, newFilterLinkFromC(lcs[i])) - } - return +func (bfc *BuffersinkFilterContext) SampleAspectRatio() Rational { + return newRationalFromC(C.av_buffersink_get_sample_aspect_ratio(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) SampleFormat() SampleFormat { + return SampleFormat(C.av_buffersink_get_format(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) SampleRate() int { + return int(C.av_buffersink_get_sample_rate(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) TimeBase() Rational { + return newRationalFromC(C.av_buffersink_get_time_base(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) Width() int { + return int(C.av_buffersink_get_w(bfc.fc.c)) +} + +type BuffersrcFilterContext struct { + fc *FilterContext +} + +func newBuffersrcFilterContext(fc *FilterContext) *BuffersrcFilterContext { + return &BuffersrcFilterContext{fc: fc} } -func (fc *FilterContext) Outputs() (ls []*FilterLink) { - lcs := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.AVFilterLink)(nil))](*C.AVFilterLink))(unsafe.Pointer(fc.c.outputs)) - for i := 0; i < fc.NbOutputs(); i++ { - ls = append(ls, newFilterLinkFromC(lcs[i])) +func (bfc *BuffersrcFilterContext) AddFrame(f *Frame, fs BuffersrcFlags) error { + var cf *C.AVFrame + if f != nil { + cf = f.c } - return + return newError(C.av_buffersrc_add_frame_flags(bfc.fc.c, cf, C.int(fs))) +} + +func (bfc *BuffersrcFilterContext) FilterContext() *FilterContext { + return bfc.fc } diff --git a/filter_graph.go b/filter_graph.go index abba8de..077e87d 100644 --- a/filter_graph.go +++ b/filter_graph.go @@ -10,6 +10,8 @@ import ( // https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavfilter/avfilter.h#L861 type FilterGraph struct { c *C.AVFilterGraph + // We need to store filter contexts to clean classer once filter graph is freed + fcs []*FilterContext } func newFilterGraphFromC(c *C.AVFilterGraph) *FilterGraph { @@ -28,9 +30,25 @@ func AllocFilterGraph() *FilterGraph { } func (g *FilterGraph) Free() { - classers.del(g) if g.c != nil { + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(g) + var cfcs []Classer + for _, fc := range g.fcs { + cfcs = append(cfcs, newClonedClasser(fc)) + } C.avfilter_graph_free(&g.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + for _, cfc := range cfcs { + if cfc != nil { + classers.del(cfc) + } + } + if c != nil { + classers.del(c) + } } } @@ -80,7 +98,25 @@ func (g *FilterGraph) NewFilterContext(f *Filter, name string, args FilterArgs) if err := newError(C.avfilter_graph_create_filter(&c, f.c, cn, ca, nil, g.c)); err != nil { return nil, err } - return newFilterContext(c), nil + fc := newFilterContext(c) + g.fcs = append(g.fcs, fc) + return fc, nil +} + +func (g *FilterGraph) NewBuffersinkFilterContext(f *Filter, name string, args FilterArgs) (*BuffersinkFilterContext, error) { + fc, err := g.NewFilterContext(f, name, args) + if err != nil { + return nil, err + } + return newBuffersinkFilterContext(fc), nil +} + +func (g *FilterGraph) NewBuffersrcFilterContext(f *Filter, name string, args FilterArgs) (*BuffersrcFilterContext, error) { + fc, err := g.NewFilterContext(f, name, args) + if err != nil { + return nil, err + } + return newBuffersrcFilterContext(fc), nil } func (g *FilterGraph) Parse(content string, inputs, outputs *FilterInOut) error { diff --git a/filter_graph_test.go b/filter_graph_test.go index 14dceee..8f4bd5f 100644 --- a/filter_graph_test.go +++ b/filter_graph_test.go @@ -31,13 +31,14 @@ func TestFilterGraph(t *testing.T) { target string withError bool } - type link struct { + type buffersink struct { channelLayout ChannelLayout colorRange ColorRange colorSpace ColorSpace frameRate Rational height int mediaType MediaType + name string pixelFormat PixelFormat sampleAspectRatio Rational sampleFormat SampleFormat @@ -45,30 +46,32 @@ func TestFilterGraph(t *testing.T) { timeBase Rational width int } + type buffersrc struct { + name string + } type graph struct { - buffersinkExpectedInput link - buffersinkName string - buffersrcName string - commands []command - content string - s string - sources []FilterArgs + buffersink buffersink + buffersrc buffersrc + commands []command + content string + s string + sources []FilterArgs } for _, v := range []graph{ { - buffersinkExpectedInput: link{ + buffersink: buffersink{ colorRange: ColorRangeUnspecified, colorSpace: ColorSpaceUnspecified, frameRate: NewRational(4, 1), height: 8, mediaType: MediaTypeVideo, + name: "buffersink", pixelFormat: PixelFormatYuv420P, sampleAspectRatio: NewRational(2, 1), timeBase: NewRational(1, 4), width: 4, }, - buffersinkName: "buffersink", - buffersrcName: "buffer", + buffersrc: buffersrc{name: "buffer"}, commands: []command{ { args: "a", @@ -97,17 +100,17 @@ func TestFilterGraph(t *testing.T) { }, }, { - buffersinkExpectedInput: link{ + buffersink: buffersink{ channelLayout: ChannelLayoutStereo, mediaType: MediaTypeAudio, + name: "abuffersink", sampleFormat: SampleFormatS16, sampleRate: 3, timeBase: NewRational(1, 4), }, - buffersinkName: "abuffersink", - buffersrcName: "abuffer", - content: "[input_1]aformat=sample_fmts=s16:channel_layouts=stereo:sample_rates=3,asettb=1/4", - s: " +---------------+\nParsed_asettb_1:default--[3Hz s16:stereo]--default| filter_out |\n | (abuffersink) |\n +---------------+\n\n+-------------+\n| filter_in_1 |default--[2Hz fltp:mono]--auto_aresample_0:default\n| (abuffer) |\n+-------------+\n\n +------------------+\nauto_aresample_0:default--[3Hz s16:stereo]--default| Parsed_aformat_0 |default--[3Hz s16:stereo]--Parsed_asettb_1:default\n | (aformat) |\n +------------------+\n\n +-----------------+\nParsed_aformat_0:default--[3Hz s16:stereo]--default| Parsed_asettb_1 |default--[3Hz s16:stereo]--filter_out:default\n | (asettb) |\n +-----------------+\n\n +------------------+\nfilter_in_1:default--[2Hz fltp:mono]--default| auto_aresample_0 |default--[3Hz s16:stereo]--Parsed_aformat_0:default\n | (aresample) |\n +------------------+\n\n", + buffersrc: buffersrc{name: "abuffer"}, + content: "[input_1]aformat=sample_fmts=s16:channel_layouts=stereo:sample_rates=3,asettb=1/4", + s: " +---------------+\nParsed_asettb_1:default--[3Hz s16:stereo]--default| filter_out |\n | (abuffersink) |\n +---------------+\n\n+-------------+\n| filter_in_1 |default--[2Hz fltp:mono]--auto_aresample_0:default\n| (abuffer) |\n+-------------+\n\n +------------------+\nauto_aresample_0:default--[3Hz s16:stereo]--default| Parsed_aformat_0 |default--[3Hz s16:stereo]--Parsed_asettb_1:default\n | (aformat) |\n +------------------+\n\n +-----------------+\nParsed_aformat_0:default--[3Hz s16:stereo]--default| Parsed_asettb_1 |default--[3Hz s16:stereo]--filter_out:default\n | (asettb) |\n +-----------------+\n\n +------------------+\nfilter_in_1:default--[2Hz fltp:mono]--default| auto_aresample_0 |default--[3Hz s16:stereo]--Parsed_aformat_0:default\n | (aresample) |\n +------------------+\n\n", sources: []FilterArgs{ { "channel_layout": ChannelLayoutMono.String(), @@ -122,21 +125,21 @@ func TestFilterGraph(t *testing.T) { require.NotNil(t, fg) defer fg.Free() - buffersrc := FindFilterByName(v.buffersrcName) + buffersrc := FindFilterByName(v.buffersrc.name) require.NotNil(t, buffersrc) - buffersink := FindFilterByName(v.buffersinkName) + buffersink := FindFilterByName(v.buffersink.name) require.NotNil(t, buffersink) - buffersinkContext, err := fg.NewFilterContext(buffersink, "filter_out", nil) + buffersinkContext, err := fg.NewBuffersinkFilterContext(buffersink, "filter_out", nil) require.NoError(t, err) - cl = buffersinkContext.Class() + cl = buffersinkContext.FilterContext().Class() require.NotNil(t, cl) require.Equal(t, "AVFilter", cl.Name()) inputs := AllocFilterInOut() defer inputs.Free() inputs.SetName("out") - inputs.SetFilterContext(buffersinkContext) + inputs.SetFilterContext(buffersinkContext.FilterContext()) inputs.SetPadIdx(0) inputs.SetNext(nil) @@ -147,15 +150,15 @@ func TestFilterGraph(t *testing.T) { } }() - var buffersrcContexts []*FilterContext + var buffersrcContexts []*BuffersrcFilterContext for idx, src := range v.sources { - buffersrcContext, err := fg.NewFilterContext(buffersrc, fmt.Sprintf("filter_in_%d", idx+1), src) + buffersrcContext, err := fg.NewBuffersrcFilterContext(buffersrc, fmt.Sprintf("filter_in_%d", idx+1), src) require.NoError(t, err) buffersrcContexts = append(buffersrcContexts, buffersrcContext) o := AllocFilterInOut() o.SetName(fmt.Sprintf("input_%d", idx+1)) - o.SetFilterContext(buffersrcContext) + o.SetFilterContext(buffersrcContext.FilterContext()) o.SetPadIdx(0) o.SetNext(outputs) @@ -165,33 +168,21 @@ func TestFilterGraph(t *testing.T) { require.NoError(t, fg.Parse(v.content, inputs, outputs)) require.NoError(t, fg.Configure()) - require.Equal(t, 1, buffersinkContext.NbInputs()) - links := buffersinkContext.Inputs() - require.Equal(t, 1, len(links)) - e, g := v.buffersinkExpectedInput, links[0] - require.Equal(t, e.frameRate, g.FrameRate()) - require.Equal(t, e.mediaType, g.MediaType()) - require.Equal(t, e.timeBase, g.TimeBase()) - switch e.mediaType { + require.Equal(t, v.buffersink.frameRate, buffersinkContext.FrameRate()) + require.Equal(t, v.buffersink.mediaType, buffersinkContext.MediaType()) + require.Equal(t, v.buffersink.timeBase, buffersinkContext.TimeBase()) + switch v.buffersink.mediaType { case MediaTypeAudio: - require.True(t, e.channelLayout.Equal(g.ChannelLayout())) - require.Equal(t, e.sampleFormat, g.SampleFormat()) - require.Equal(t, e.sampleRate, g.SampleRate()) + require.True(t, v.buffersink.channelLayout.Equal(buffersinkContext.ChannelLayout())) + require.Equal(t, v.buffersink.sampleFormat, buffersinkContext.SampleFormat()) + require.Equal(t, v.buffersink.sampleRate, buffersinkContext.SampleRate()) default: - require.Equal(t, e.colorRange, g.ColorRange()) - require.Equal(t, e.colorSpace, g.ColorSpace()) - require.Equal(t, e.height, g.Height()) - require.Equal(t, e.pixelFormat, g.PixelFormat()) - require.Equal(t, e.sampleAspectRatio, g.SampleAspectRatio()) - require.Equal(t, e.width, g.Width()) - } - - for _, buffersrcContext := range buffersrcContexts { - require.Equal(t, 0, buffersrcContext.NbInputs()) - require.Equal(t, 1, buffersrcContext.NbOutputs()) - links := buffersrcContext.Outputs() - require.Equal(t, 1, len(links)) - require.Equal(t, v.buffersinkExpectedInput.mediaType, links[0].MediaType()) + require.Equal(t, v.buffersink.colorRange, buffersinkContext.ColorRange()) + require.Equal(t, v.buffersink.colorSpace, buffersinkContext.ColorSpace()) + require.Equal(t, v.buffersink.height, buffersinkContext.Height()) + require.Equal(t, v.buffersink.pixelFormat, buffersinkContext.PixelFormat()) + require.Equal(t, v.buffersink.sampleAspectRatio, buffersinkContext.SampleAspectRatio()) + require.Equal(t, v.buffersink.width, buffersinkContext.Width()) } require.Equal(t, v.s, fg.String()) diff --git a/filter_link.go b/filter_link.go deleted file mode 100644 index a94929c..0000000 --- a/filter_link.go +++ /dev/null @@ -1,65 +0,0 @@ -package astiav - -//#include -import "C" - -// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavfilter/avfilter.h#L471 -type FilterLink struct { - c *C.AVFilterLink -} - -func newFilterLinkFromC(c *C.AVFilterLink) *FilterLink { - if c == nil { - return nil - } - return &FilterLink{c: c} -} - -func (l *FilterLink) ChannelLayout() ChannelLayout { - v, _ := newChannelLayoutFromC(&l.c.ch_layout).clone() - return v -} - -func (l *FilterLink) ColorRange() ColorRange { - return ColorRange(l.c.color_range) -} - -func (l *FilterLink) ColorSpace() ColorSpace { - return ColorSpace(l.c.colorspace) -} - -func (l *FilterLink) FrameRate() Rational { - return newRationalFromC(l.c.frame_rate) -} - -func (l *FilterLink) Height() int { - return int(l.c.h) -} - -func (l *FilterLink) MediaType() MediaType { - return MediaType(l.c._type) -} - -func (l *FilterLink) PixelFormat() PixelFormat { - return PixelFormat(l.c.format) -} - -func (l *FilterLink) SampleAspectRatio() Rational { - return newRationalFromC(l.c.sample_aspect_ratio) -} - -func (l *FilterLink) SampleFormat() SampleFormat { - return SampleFormat(l.c.format) -} - -func (l *FilterLink) SampleRate() int { - return int(l.c.sample_rate) -} - -func (l *FilterLink) TimeBase() Rational { - return newRationalFromC(l.c.time_base) -} - -func (l *FilterLink) Width() int { - return int(l.c.w) -} diff --git a/format_context.go b/format_context.go index f8dacfd..c41d2e1 100644 --- a/format_context.go +++ b/format_context.go @@ -51,8 +51,15 @@ func AllocOutputFormatContext(of *OutputFormat, formatName, filename string) (*F } func (fc *FormatContext) Free() { - classers.del(fc) + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(fc) C.avformat_free_context(fc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } } func (fc *FormatContext) BitRate() int64 { @@ -192,12 +199,23 @@ func (fc *FormatContext) OpenInput(url string, fmt *InputFormat, d *Dictionary) } func (fc *FormatContext) CloseInput() { - if pb := fc.Pb(); pb != nil { - classers.del(pb) - } - classers.del(fc) if fc.c != nil { + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(fc) + var cpb Classer + if pb := fc.Pb(); pb != nil { + cpb = newClonedClasser(pb) + } C.avformat_close_input(&fc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if cpb != nil { + classers.del(cpb) + } + if c != nil { + classers.del(c) + } } } diff --git a/format_context_test.go b/format_context_test.go index bbe0616..f816fe2 100644 --- a/format_context_test.go +++ b/format_context_test.go @@ -7,7 +7,7 @@ import ( ) func TestFormatContext(t *testing.T) { - fc1, err := globalHelper.inputFormatContext("video.mp4") + fc1, err := globalHelper.inputFormatContext("video.mp4", nil) require.NoError(t, err) ss := fc1.Streams() require.Len(t, ss, 2) diff --git a/frame.c b/frame.c new file mode 100644 index 0000000..74b3916 --- /dev/null +++ b/frame.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +int astiavSamplesCopyToBuffer(uint8_t* dst, int dst_size, const uint8_t * const src_data[8], int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align) { + int linesize, buffer_size, nb_planes, i; + + buffer_size = av_samples_get_buffer_size(&linesize, nb_channels, nb_samples, sample_fmt, align); + if (buffer_size > dst_size || buffer_size < 0) return AVERROR(EINVAL); + + nb_planes = buffer_size / linesize; + + for (i = 0; i < nb_planes; i++) { + const uint8_t *src = src_data[i]; + memcpy(dst, src, linesize); + dst += linesize; + src += linesize; + } + return buffer_size; +} \ No newline at end of file diff --git a/frame.go b/frame.go index ee91c15..f50a493 100644 --- a/frame.go +++ b/frame.go @@ -5,6 +5,7 @@ package astiav //#include //#include //#include +//#include "frame.h" import "C" import ( "unsafe" @@ -98,7 +99,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 } @@ -106,7 +107,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 } @@ -121,6 +122,26 @@ func (f *Frame) ImageFillBlack() error { return newError(C.av_image_fill_black(&f.c.data[0], &linesize[0], (C.enum_AVPixelFormat)(f.c.format), (C.enum_AVColorRange)(f.c.color_range), f.c.width, f.c.height)) } +func (f *Frame) SamplesBufferSize(align int) (int, error) { + ret := C.av_samples_get_buffer_size(nil, f.c.ch_layout.nb_channels, f.c.nb_samples, (C.enum_AVSampleFormat)(f.c.format), C.int(align)) + if err := newError(ret); err != nil { + return 0, err + } + return int(ret), nil +} + +func (f *Frame) SamplesCopyToBuffer(b []byte, align int) (int, error) { + ret := C.astiavSamplesCopyToBuffer((*C.uint8_t)(unsafe.Pointer(&b[0])), C.int(len(b)), &f.c.data[0], f.c.ch_layout.nb_channels, f.c.nb_samples, (C.enum_AVSampleFormat)(f.c.format), C.int(align)) + if err := newError(ret); err != nil { + return 0, err + } + return int(ret), nil +} + +func (f *Frame) SamplesFillSilence() error { + return newError(C.av_samples_set_silence(&f.c.data[0], 0, f.c.nb_samples, f.c.ch_layout.nb_channels, (C.enum_AVSampleFormat)(f.c.format))) +} + func (f *Frame) Linesize() [NumDataPointers]int { o := [NumDataPointers]int{} for i := 0; i < int(NumDataPointers); i++ { @@ -232,3 +253,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)) +} diff --git a/frame.h b/frame.h new file mode 100644 index 0000000..88fbcc3 --- /dev/null +++ b/frame.h @@ -0,0 +1,4 @@ +#include +#include + +int astiavSamplesCopyToBuffer(uint8_t* dst, int dst_size, const uint8_t * const src_data[8], int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align); \ No newline at end of file diff --git a/frame_data.go b/frame_data.go index 2571b05..3216526 100644 --- a/frame_data.go +++ b/frame_data.go @@ -1,6 +1,8 @@ package astiav //#include +//#include +//#include //#include "macros.h" import "C" import ( @@ -8,6 +10,7 @@ import ( "fmt" "image" "strings" + "unsafe" ) type FrameData struct { @@ -16,9 +19,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 +39,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 +137,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 { @@ -164,94 +255,162 @@ func newFrameDataFrame(f *Frame) *frameDataFrame { } func (f *frameDataFrame) bytes(align int) ([]byte, error) { - switch { - // Video - case f.height() > 0 && f.width() > 0: - // Get buffer size - s, err := f.f.ImageBufferSize(align) - if err != nil { - return nil, fmt.Errorf("astiav: getting image buffer size failed: %w", err) - } + // Get funcs + var bufferSizeFunc func(int) (int, error) + var copyToBufferFunc func([]byte, int) (int, error) + switch f.mediaType() { + case MediaTypeAudio: + bufferSizeFunc = f.f.SamplesBufferSize + copyToBufferFunc = f.f.SamplesCopyToBuffer + case MediaTypeVideo: + bufferSizeFunc = f.f.ImageBufferSize + copyToBufferFunc = f.f.ImageCopyToBuffer + default: + return nil, errors.New("astiav: media type not implemented") + } - // Invalid buffer size - if s == 0 { - return nil, errors.New("astiav: invalid image buffer size") - } + // Get buffer size + s, err := bufferSizeFunc(align) + if err != nil { + return nil, fmt.Errorf("astiav: getting buffer size failed: %w", err) + } + + // Invalid buffer size + if s == 0 { + return nil, errors.New("astiav: invalid buffer size") + } - // Create buffer - b := make([]byte, s) + // Create buffer + b := make([]byte, s) - // Copy image to buffer - if _, err = f.f.ImageCopyToBuffer(b, align); err != nil { - return nil, fmt.Errorf("astiav: copying image to buffer failed: %w", err) + // Copy to buffer + if _, err = copyToBufferFunc(b, align); err != nil { + return nil, fmt.Errorf("astiav: copying to buffer failed: %w", err) + } + return b, nil +} + +func (f *frameDataFrame) copyPlanes(ps []frameDataPlane) error { + // Check writability + if !f.f.IsWritable() { + return errors.New("astiav: frame is not writable") + } + + // Prepare data + 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])) } - return b, nil + + // Convert linesize + clinesizes[i] = C.int(p.linesize) + } + + // Copy data + switch f.mediaType() { + case MediaTypeAudio: + C.av_samples_copy(&f.f.c.data[0], &cdata[0], 0, 0, f.f.c.nb_samples, f.f.c.ch_layout.nb_channels, (C.enum_AVSampleFormat)(f.f.c.format)) + case MediaTypeVideo: + 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) + default: + return errors.New("astiav: media type not implemented") } - return nil, errors.New("astiav: frame type not implemented") + return nil } func (f *frameDataFrame) height() int { return f.f.Height() } +func (f *frameDataFrame) mediaType() MediaType { + switch { + // Audio + case f.f.NbSamples() > 0: + return MediaTypeAudio + // Video + case f.f.Height() > 0 && f.f.Width() > 0: + return MediaTypeVideo + default: + return MediaTypeUnknown + } +} + 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) { + // Get line and plane sizes + var linesizes [8]int + var planeSizes [8]int + switch f.mediaType() { + case MediaTypeAudio: + // Get buffer size + var cLinesize C.int + cBufferSize := C.av_samples_get_buffer_size(&cLinesize, f.f.c.ch_layout.nb_channels, f.f.c.nb_samples, (C.enum_AVSampleFormat)(f.f.c.format), C.int(align)) + if err := newError(cBufferSize); err != nil { + return nil, fmt.Errorf("astiav: getting buffer size failed: %w", err) + } - switch { - // Video - case f.height() > 0 && f.width() > 0: + // Update line and plane sizes + for i := 0; i < int(cBufferSize/cLinesize); i++ { + linesizes[i] = int(cLinesize) + planeSizes[i] = int(cLinesize) + } + case MediaTypeVideo: // 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 cLinesizes [8]C.int + if err := newError(C.av_image_fill_linesizes(&cLinesizes[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 cAlignedLinesizes [8]C.ptrdiff_t for i := 0; i < 4; i++ { - alignedLinesize[i] = C.astiavFFAlign(linesize[i], C.int(align)) + cAlignedLinesizes[i] = C.astiavFFAlign(cLinesizes[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 { + var cPlaneSizes [8]C.size_t + if err := newError(C.av_image_fill_plane_sizes(&cPlaneSizes[0], (C.enum_AVPixelFormat)(f.f.c.format), f.f.c.height, &cAlignedLinesizes[0])); err != nil { return nil, fmt.Errorf("astiav: getting plane sizes failed: %w", err) } - // Loop through plane sizes - var ps []frameDataPlane - start := 0 - for idx, planeSize := range planeSizes { - // Get end - end := start + int(planeSize) - if len(b) < end { - return nil, fmt.Errorf("astiav: buffer length %d is invalid for [%d:%d]", len(b), start, end) - } - - // Append plane - ps = append(ps, frameDataPlane{ - bytes: b[start:end], - linesize: int(alignedLinesize[idx]), - }) - - // Update start - start += int(planeSize) + // Update line and plane sizes + for i := range cPlaneSizes { + linesizes[i] = int(cAlignedLinesizes[i]) + planeSizes[i] = int(cPlaneSizes[i]) + } + default: + return nil, errors.New("astiav: media type not implemented") + } + + // Loop through plane sizes + var ps []frameDataPlane + start := 0 + for i := range planeSizes { + // Get end + end := start + planeSizes[i] + if len(b) < end { + return nil, fmt.Errorf("astiav: buffer length %d is invalid for [%d:%d]", len(b), start, end) } - return ps, nil + + // Append plane + ps = append(ps, frameDataPlane{ + bytes: b[start:end], + linesize: linesizes[i], + }) + + // Update start + start = end } - return nil, errors.New("astiav: frame type not implemented") + return ps, nil } func (f *frameDataFrame) width() int { diff --git a/frame_data_test.go b/frame_data_test.go index a44dee4..7739e52 100644 --- a/frame_data_test.go +++ b/frame_data_test.go @@ -10,17 +10,23 @@ import ( ) type mockedFrameDataFrame struct { - h int - imageBytes []byte - pf PixelFormat - planes_ []frameDataPlane - w int + copiedPlanes []frameDataPlane + h int + onBytes func(align int) ([]byte, error) + onPlanes func(b []byte, align int) ([]frameDataPlane, error) + pf PixelFormat + 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 { @@ -113,16 +119,16 @@ func TestFrameDataInternal(t *testing.T) { } } - fdf.h = 1 - fdf.imageBytes = []byte{0, 1, 2, 3} - fdf.w = 2 - b, err := fd.Bytes(0) + b1 := []byte{0, 1, 2, 3} + fdf.onBytes = func(align int) ([]byte, error) { return b1, nil } + b2, err := fd.Bytes(0) require.NoError(t, err) - require.Equal(t, fdf.imageBytes, b) + require.Equal(t, b1, b2) + fdf.h = 1 + fdf.w = 2 for _, v := range []struct { e image.Image - err bool i image.Image pixelFormat PixelFormat planes []frameDataPlane @@ -326,48 +332,240 @@ 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 { - require.Equal(t, v.e, v.i) - } + 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) } } func TestFrameData(t *testing.T) { for _, v := range []struct { ext string + ifmt *InputFormat + md MediaType name string }{ + { + ext: "pcm", + ifmt: FindInputFormat("s16le"), + md: MediaTypeAudio, + name: "audio-s16le", + }, { ext: "png", + md: MediaTypeVideo, name: "image-rgba", }, { ext: "h264", + md: MediaTypeVideo, name: "video-yuv420p", }, } { - f, err := globalHelper.inputLastFrame(v.name+"."+v.ext, MediaTypeVideo) + f1, err := globalHelper.inputLastFrame(v.name+"."+v.ext, v.md, v.ifmt) 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() - require.NoError(t, err) - require.NoError(t, fd.ToImage(i1)) - b4 := []byte(fmt.Sprintf("%+v", i1)) - b5, err := os.ReadFile("testdata/" + v.name + "-struct") + var i1 image.Image + switch v.md { + case MediaTypeVideo: + i1, err = fd1.GuessImageFormat() + require.NoError(t, err) + 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, b5, b4) + } + + f2 := AllocFrame() + defer f2.Free() + fd2 := f2.Data() + + align := 0 + switch v.md { + case MediaTypeAudio: + f2.SetChannelLayout(f1.ChannelLayout()) + f2.SetNbSamples(f1.NbSamples()) + f2.SetSampleFormat(f1.SampleFormat()) + f2.SetSampleRate(f1.SampleRate()) + require.NoError(t, f2.AllocBuffer(align)) + require.NoError(t, f2.AllocSamples(align)) + case MediaTypeVideo: + align = 1 + f2.SetHeight(f1.Height()) + f2.SetPixelFormat(f1.PixelFormat()) + f2.SetWidth(f1.Width()) + require.NoError(t, f2.AllocBuffer(align)) + require.NoError(t, f2.AllocImage(align)) + } + + switch v.md { + case MediaTypeVideo: + 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) + } + + switch v.md { + case MediaTypeAudio: + require.NoError(t, f2.SamplesFillSilence()) + case MediaTypeVideo: + require.NoError(t, f2.ImageFillBlack()) + } + require.NoError(t, fd2.SetBytes(b1, align)) + b1[0] -= 1 + b8, err := fd2.Bytes(align) require.NoError(t, err) - require.Equal(t, b4, b5) + b9 := []byte(fmt.Sprintf("%+v", b8)) + require.Equal(t, b3, b9) + f3 := AllocFrame() + defer f3.Free() + require.NoError(t, f3.Ref(f2)) + switch v.md { + case MediaTypeVideo: + require.Error(t, fd2.FromImage(i1)) + } + require.Error(t, fd2.SetBytes(b1, align)) + f2.MakeWritable() + switch v.md { + case MediaTypeVideo: + require.NoError(t, fd2.FromImage(i1)) + } + require.NoError(t, fd2.SetBytes(b1, align)) } } diff --git a/frame_test.go b/frame_test.go index 8a942b9..e375098 100644 --- a/frame_test.go +++ b/frame_test.go @@ -9,7 +9,7 @@ import ( ) func TestFrame(t *testing.T) { - f1, err := globalHelper.inputLastFrame("video.mp4", MediaTypeVideo) + f1, err := globalHelper.inputLastFrame("video.mp4", MediaTypeVideo, nil) require.NoError(t, err) // Should be "{384, 192, 192, 0, 0, 0, 0, 0}" but for some reason it"s "{320, 160, 160, 0, 0, 0, 0, 0}" // on darwin when testing using github @@ -75,10 +75,18 @@ func TestFrame(t *testing.T) { f4.SetChannelLayout(ChannelLayoutStereo) f4.SetSampleFormat(SampleFormatS16) f4.SetSampleRate(48000) - err = f4.AllocBuffer(0) + align := 0 + require.NoError(t, f4.AllocBuffer(align)) + require.NoError(t, f4.AllocSamples(align)) + require.NoError(t, f4.SamplesFillSilence()) + n, err := f4.SamplesBufferSize(align) require.NoError(t, err) - err = f4.AllocSamples(0) + require.Equal(t, 3840, n) + b := make([]byte, n) + n, err = f4.SamplesCopyToBuffer(b, align) require.NoError(t, err) + require.Equal(t, 3840, n) + require.Equal(t, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, b) f5 := AllocFrame() require.NotNil(t, f5) @@ -103,16 +111,22 @@ func TestFrame(t *testing.T) { f6.SetHeight(2) f6.SetPixelFormat(PixelFormatYuv420P) f6.SetWidth(4) - const align = 1 + align = 1 require.NoError(t, f6.AllocBuffer(align)) require.NoError(t, f6.AllocImage(align)) require.NoError(t, f6.ImageFillBlack()) - n, err := f6.ImageBufferSize(align) + n, err = f6.ImageBufferSize(align) require.NoError(t, err) require.Equal(t, 12, n) - b := make([]byte, n) + b = make([]byte, n) n, err = f6.ImageCopyToBuffer(b, align) 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()) } diff --git a/io_context.go b/io_context.go index b1b26b3..1156b19 100644 --- a/io_context.go +++ b/io_context.go @@ -6,6 +6,7 @@ import "C" import ( "errors" "fmt" + "io" "sync" "unsafe" ) @@ -121,15 +122,23 @@ func (ic *IOContext) Class() *Class { } func (ic *IOContext) Close() error { - classers.del(ic) if ic.c != nil { - return newError(C.avio_closep(&ic.c)) + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(ic) + if err := newError(C.avio_closep(&ic.c)); err != nil { + return err + } + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } } return nil } func (ic *IOContext) Free() { - classers.del(ic) if ic.c != nil { if ic.c.buffer != nil { C.av_freep(unsafe.Pointer(&ic.c.buffer)) @@ -138,7 +147,15 @@ func (ic *IOContext) Free() { C.free(ic.handlerID) ic.handlerID = nil } + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(ic) C.avio_context_free(&ic.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } } return } @@ -241,6 +258,8 @@ func goAstiavIOContextReadFunc(opaque unsafe.Pointer, buf *C.uint8_t, bufSize C. var e Error if errors.As(err, &e) { return C.int(e) + } else if errors.Is(err, io.EOF) { + return C.AVERROR_EOF } return C.AVERROR_UNKNOWN } diff --git a/io_context_test.go b/io_context_test.go index 0f82882..09d4576 100644 --- a/io_context_test.go +++ b/io_context_test.go @@ -1,6 +1,7 @@ package astiav import ( + "io" "os" "path/filepath" "testing" @@ -9,34 +10,48 @@ import ( ) func TestIOContext(t *testing.T) { - var seeked bool - rb := []byte("read") - wb := []byte("write") - var written []byte - c, err := AllocIOContext(8, true, func(b []byte) (int, error) { - copy(b, rb) - return len(rb), nil - }, func(offset int64, whence int) (n int64, err error) { - seeked = true - return offset, nil - }, func(b []byte) (int, error) { - written = make([]byte, len(b)) - copy(written, b) - return len(b), nil + t.Run("read write seek", func(t *testing.T) { + var seeked bool + rb := []byte("read") + wb := []byte("write") + var written []byte + c, err := AllocIOContext(8, true, func(b []byte) (int, error) { + copy(b, rb) + return len(rb), nil + }, func(offset int64, whence int) (n int64, err error) { + seeked = true + return offset, nil + }, func(b []byte) (int, error) { + written = make([]byte, len(b)) + copy(written, b) + return len(b), nil + }) + require.NoError(t, err) + defer c.Free() + b := make([]byte, 6) + n, err := c.Read(b) + require.NoError(t, err) + require.Equal(t, 4, n) + require.Equal(t, rb, b[:n]) + _, err = c.Seek(2, 0) + require.NoError(t, err) + require.True(t, seeked) + c.Write(wb) + c.Flush() + require.Equal(t, wb, written) + }) + + t.Run("io.EOF is mapped to AVERROR_EOF when reading", func(t *testing.T) { + c, err := AllocIOContext(8, false, func(b []byte) (int, error) { + return 0, io.EOF + }, nil, nil) + require.NoError(t, err) + defer c.Free() + b := make([]byte, 100) + n, err := c.Read(b) + require.ErrorIs(t, err, ErrEof) + require.Equal(t, 0, n) }) - require.NoError(t, err) - defer c.Free() - b := make([]byte, 6) - n, err := c.Read(b) - require.NoError(t, err) - require.Equal(t, 4, n) - require.Equal(t, rb, b[:n]) - _, err = c.Seek(2, 0) - require.NoError(t, err) - require.True(t, seeked) - c.Write(wb) - c.Flush() - require.Equal(t, wb, written) } func TestOpenIOContext(t *testing.T) { diff --git a/software_scale_context.go b/software_scale_context.go index 9f4f178..d225202 100644 --- a/software_scale_context.go +++ b/software_scale_context.go @@ -60,8 +60,15 @@ func CreateSoftwareScaleContext(srcW, srcH int, srcFormat PixelFormat, dstW, dst } func (ssc *SoftwareScaleContext) Free() { - classers.del(ssc) + // Make sure to clone the classer before freeing the object since + // the C free method may reset the pointer + c := newClonedClasser(ssc) C.sws_freeContext(ssc.c) + // Make sure to remove from classers after freeing the object since + // the C free method may use methods needing the classer + if c != nil { + classers.del(c) + } } var _ Classer = (*SoftwareScaleContext)(nil) diff --git a/software_scale_context_test.go b/software_scale_context_test.go index 99b524e..88681ec 100644 --- a/software_scale_context_test.go +++ b/software_scale_context_test.go @@ -93,7 +93,7 @@ func TestSoftwareScaleContext(t *testing.T) { require.Equal(t, w, srcW) require.Equal(t, h, srcH) - f4, err := globalHelper.inputLastFrame("image-rgba.png", MediaTypeVideo) + f4, err := globalHelper.inputLastFrame("image-rgba.png", MediaTypeVideo, nil) require.NoError(t, err) f5 := AllocFrame() diff --git a/stream_test.go b/stream_test.go index e374b8f..0703496 100644 --- a/stream_test.go +++ b/stream_test.go @@ -7,7 +7,7 @@ import ( ) func TestStream(t *testing.T) { - fc, err := globalHelper.inputFormatContext("video.mp4") + fc, err := globalHelper.inputFormatContext("video.mp4", nil) require.NoError(t, err) ss := fc.Streams() require.Len(t, ss, 2) diff --git a/testdata/audio-s16le-bytes b/testdata/audio-s16le-bytes new file mode 100755 index 0000000..7d4c4bd --- /dev/null +++ b/testdata/audio-s16le-bytes @@ -0,0 +1 @@ +[61 254 101 0 68 254 135 255 100 254 242 254 174 254 186 254 45 255 168 254 215 255 143 254 154 0 148 254 79 1 199 254 201 1 255 254 242 1 58 255 199 1 130 255 104 1 192 255 8 1 7 0 184 0 101 0 113 0 181 0 60 0 240 0 32 0 44 1 22 0 51 1 13 0 218 0 235 255 63 0 159 255 119 255 96 255 174 254 111 255 54 254 199 255 19 254 63 0 8 254 187 0 13 254 35 1 52 254 92 1 123 254 90 1 225 254 21 1 72 255 145 0 133 255 237 255 165 255 99 255 208 255 27 255 11 0 255 254 75 0 220 254 132 0 174 254 145 0 154 254 96 0 180 254 1 0 248 254 137 255 94 255 22 255 227 255 219 254 125 0 221 254 19 1 244 254 147 1 27 255 249 1 111 255 55 2 237 255 50 2 104 0 217 1 197 0 61 1 15 1 140 0 95 1 238 255 188 1 124 255 14 2 74 255 62 2 94 255 78 2 138 255 55 2 154 255 216 1 143 255 42 1 130 255 85 0 138 255 149 255 178 255 25 255 242 255 222 254 66 0 209 254 170 0 244 254 16 1 59 255 68 1 130 255 79 1 212 255 72 1 67 0 18 1 163 0 166 0 220 0 67 0 26 1 12 0 110 1 239 255 182 1 210 255 226 1 154 255 228 1 64 255 160 1 244 254 6 1 232 254 42 0 14 255 84 255 69 255 214 254 127 255 165 254 202 255 133 254 56 0 120 254 169 0 154 254 226 0 226 254 204 0 64 255 120 0 159 255 232 255 216 255 31 255 240 255 78 254 32 0 187 253 125 0 133 253 232 0 154 253 60 1 211 253 84 1 12 254 24 1 60 254 139 0 99 254 189 255 144 254 218 254 228 254 52 254 111 255 250 253 19 0 7 254 183 0 48 254 85 1 119 254 213 1 217 254 26 2 79 255 26 2 214 255 192 1 83 0 11 1 163 0 82 0 217 0 237 255 16 1 207 255 68 1 195 255 127 1 187 255 175 1 183 255 142 1 173 255 254 0 165 255 42 0 175 255 68 255 223 255 145 254 64 0 67 254 189 0 39 254 45 1 8 254 122 1 19 254 169 1 103 254 198 1 218 254 207 1 84 255 186 1 204 255 126 1 46 0 38 1 132 0 197 0 221 0 107 0 46 1 25 0 121 1 208 255 173 1 150 255 150 1 110 255 49 1 68 255 156 0 14 255 218 255 243 254 9 255 39 255 134 254 152 255 107 254 9 0 121 254 121 0 148 254 4 1 222 254 122 1 98 255 145 1 240 255 80 1 88 0 214 0 164 0 40 0 247 0 114 255 77 1 245 254 135 1 182 254 166 1 136 254 178 1 74 254 156 1 10 254 79 1 240 253 199 0 9 254 10 0 72 254 70 255 171 254 192 254 40 255 141 254 151 255 136 254 249 255 161 254 113 0 233 254 222 0 93 255 5 1 230 255 236 0 111 0 168 0 230 0 66 0 71 1 214 255 157 1 138 255 232 1 102 255 28 2 69 255 50 2 11 255 18 2 201 254 168 1 163 254 12 1 149 254 85 0 149 254 142 255 173 254 240 254 220 254 160 254 36 255 123 254 157 255 104 254 60 0 138 254 204 0 220 254 56 1 54 255 126 1 136 255 148 1 219 255 137 1 46 0 119 1 124 0 90 1 184 0 42 1 237 0 250 0 46 1 216 0 90 1 176 0 57 1 104 0 189 0 7 0 250 255 171 255 25 255 117 255 106 254 107 255 20 254 142 255 235 253 228 255 212 253 115 0 239 253 31 1 69 254 163 1 181 254 200 1 36 255 141 1 133 255 22 1 216 255 151 0 43 0 65 0 127 0 16 0 200 0 221 255 247 0 142 255 2 1 52 255 225 0 254 254 149 0 0 255 20 0 47 255 114 255 138 255 247 254 6 0 195 254 119 0 170 254 209 0 158 254 44 1 189 254 113 1 15 255 137 1 140 255 130 1 32 0 75 1 161 0 199 0 6 1 56 0 113 1 230 255 228 1 198 255 68 2 196 255 140 2 210 255 175 2 195 255 138 2 133 255 17 2 58 255 73 1 251 254 81 0 220 254 125 255 234 254 11 255 16 255 219 254 76 255 193 254 178 255 207 254 41 0 16 255 121 0 103 255 149 0 203 255 145 0 53 0 120 0 141 0 75 0 201 0 17 0 247 0 221 255 51 1 179 255 142 1 113 255 229 1 12 255 232 1 175 254 129 1 125 254 222 0 118 254 24 0 152 254 66 255 217 254 160 254 25 255 81 254 75 255 33 254 143 255 2 254 238 255 30 254 48 0 115 254 38 0 224 254 228 255 98 255 128 255 222 255 245 254 53 0 111 254 119 0 49 254 199 0 64 254 34 1 107 254 116 1 135 254 135 1 140 254 46 1 151 254 125 0 182 254 166 255 233 254 209 254 77 255 59 254 237 255 250 253 137 0 212 253 3 1 195 253 132 1 2 254 3 2 137 254 70 2 20 255 63 2 146 255 242 1 255 255 97 1 81 0 191 0 163 0 77 0 9 1 21 0 109 1 4 0 178 1 14 0 192 1 33 0 130 1 42 0 5 1 30 0 91 0 3 0 144 255 249 255 210 254 27 0 78 254 108 0 247 253 221 0 188 253 86 1 185 253 179 1 1 254 222 1 129 254 226 1 22 255 209 1 170 255 163 1 60 0 100 1 197 0 57 1 47 1 41 1 133 1 14 1 231 1 203 0 46 2 95 0 17 2 225 255 135 1 116 255 193 0 43 255 241 255 9 255 81 255 33 255 251 254 108 255 200 254 184 255 157 254 10 0 147 254 140 0 198 254 22 1 46 255 83 1 178 255 45 1 44 0 186 0 145 0 12 0 249 0 86 255 103 1 211 254 199 1 140 254 16 2 94 254 54 2 42 254 23 2 235 253 177 1 183 253 24 1 171 253 90 0 227 253 154 255 94 254 17 255 245 254 203 254 121 255 175 254 215 255 196 254 24 0 13 255 79 0 116 255 134 0 234 255 174 0 96 0 178 0 204 0 146 0 53 1 86 0 156 1 28 0 236 1 8 0 34 2 5 0 66 2 222 255 37 2 142 255 167 1 46 255 217 0 197 254 224 255 112 254 241 254 90 254 80 254 133 254 3 254 214 254 220 253 63 255 214 253 195 255 11 254 95 0 109 254 245 0 227 254 93 1 96 255 138 1 199 255 138 1 18 0 129 1 97 0 146 1 190 0 183 1 32 1 187 1 125 1 127 1 168 1 29 1 115 1 179 0 221 0 69 0 0 0 216 255 8 255 144 255 70 254 127 255 234 253 146 255 213 253 193 255 216 253 31 0 240 253 169 0 49 254 49 1 155 254 129 1 23 255 121 1 144 255 36 1 5 0 181 0 110 0 93 0 203 0 46 0 56 1 22 0 172 1 235 255 223 1 159 255 174 1 84 255 51 1 18 255 123 0 220 254 165 255 233 254 13 255 74 255 220 254 187 255 206 254 20 0 185 254 105 0 200 254 163 0 11 255 159 0 101 255 128 0 209 255 99 0 92 0 64 0 253 0 31 0 156 1 15 0 39 2 251 255 143 2 215 255 210 2 181 255 219 2 139 255 134 2 65 255 210 1 240 254 228 0 190 254 220 255 171 254 242 254 168 254 107 254 186 254 54 254 240 254 13 254 79 255 234 253 191 255 254 253 18 0 88 254 69 0 227 254 126 0 123 255 178 0 17 0 185 0 173 0 166 0 57 1 163 0 156 1 154 0 236 1 98 0 46 2 248 255 35 2 130 255 174 1 42 255 235 0 251 254 7 0 233 254 69 255 253 254 205 254 54 255 116 254 104 255 30 254 136 255 233 253 194 255 227 253 27 0 11 254 83 0 114 254 59 0 255 254 234 255 138 255 141 255 24 0 62 255 166 0 6 255 30 1 245 254 136 1 250 254 221 1 233 254 237 1 182 254 163 1 139 254 3 1 135 254 24 0 176 254 29 255 15 255 97 254 156 255 243 253 46 0 181 253 170 0 174 253 4 1 241 253 48 1 93 254 51 1 203 254 23 1 72 255 214 0 226 255 122 0 126 0 36 0 3 1 228 255 129 1 182 255 243 1 171 255 59 2 210 255 72 2 21 0 9 2 69 0 106 1 72 0 117 0 45 0 109 255 41 0 155 254 93 0 22 254 183 0 196 253 17 1 147 253 96 1 153 253 167 1 222 253 234 1 83 254 35 2 239 254 69 2 166 255 85 2 93 0 86 2 10 1 54 2 165 1 246 1 41 2 169 1 149 2 70 1 205 2 185 0 160 2 15 0 253 1 98 255 255 0 220 254 232 255 179 254 31 255 226 254 195 254 42 255 135 254 106 255 60 254 184 255 25 254 29 0 68 254 121 0 155 254 153 0 239 254 108 0 71 255 22 0 196 255 193 255 108 0 131 255 22 1 99 255 171 1 75 255 47 2 36 255 115 2 239 254 64 2 175 254 167 1 102 254 225 0 56 254 11 0 75 254 85 255 144 254 238 254 229 254 181 254 50 255 124 254 105 255 93 254 152 255 119 254 212 255 190 254 23 0 45 255 71 0 195 255 71 0 100 0 23 0 7 1 236 255 172 1 232 255 59 2 245 255 169 2 3 0 250 2 1 0 6 3 203 255 153 2 88 255 182 1 210 254 136 0 101 254 94 255 66 254 149 254 135 254 58 254 7 255 20 254 135 255 7 254 5 0 26 254 143 0 85 254 21 1 200 254 118 1 99 255 170 1 243 255 213 1 104 0 8 2 222 0 41 2 88 1 51 2 196 1 46 2 6 2 242 1 252 1 111 1 154 1 206 0 221 0 41 0 203 255 150 255 180 254 71 255 251 253 60 255 156 253 67 255 86 253 83 255 37 253 151 255 44 253 28 0 113 253 179 0 230 253 14 1 112 254 12 1 243 254 209 0 115 255 142 0 0 0 76 0 152 0 28 0 57 1 4 0 202 1 223 255 0 2 148 255 180 1 62 255 18 1 252 254 80 0 218 254 134 255 224 254 234 254 19 255 158 254 98 255 116 254 186 255 70 254 15 0 70 254 70 0 151 254 83 0 7 255 77 0 111 255 65 0 236 255 39 0 147 0 1 0 72 1 226 255 227 1 206 255 88 2 201 255 178 2 216 255 229 2 215 255 200 2 152 255 61 2 37 255 81 1 185 254 58 0 138 254 82 255 174 254 211 254 5 255 150 254 79 255 101 254 123 255 86 254 185 255 144 254 27 0 246 254 117 0 99 255 160 0 226 255 173 0 144 0 191 0 98 1 221 0 35 2 246 0 170 2 253 0 254 2 222 0 24 3 141 0 208 2 35 0 43 2 200 255 87 1 130 255 98 0 69 255 104 255 33 255 176 254 37 255 67 254 65 255 234 253 102 255 163 253 156 255 141 253 213 255 165 253 247 255 234 253 244 255 101 254 184 255 2 255 78 255 183 255 6 255 132 0 17 255 68 1 71 255 212 1 124 255 42 2 155 255 47 2 136 255 207 1 75 255 28 1 25 255 52 0 13 255 68 255 49 255 149 254 140 255 62 254 255 255 16 254 89 0 233 253 147 0 225 253 175 0 16 254 167 0 97 254 139 0 194 254 116 0 71 255 98 0 251 255 71 0 191 0 45 0 125 1 45 0 49 2 77 0 169 2 115 0 169 2 120 0 59 2 74 0 120 1 254 255 100 0 196 255 58 255 191 255 105 254 239 255 2 254 62 0 176 253 138 0 91 253 190 0 75 253 235 0 146 253 39 1 250 253 100 1 118 254 137 1 40 255 153 1 0 0 162 1 212 0 170 1 153 1 175 1 81 2 167 1 234 2 122 1 55 3 11 1 3 3 99 0 79 2 184 255 75 1 54 255 46 0 223 254 65 255 180 254 177 254 175 254 75 254 191 254 217 253 254 254 140 253 124 255 143 253 251 255 199 253 61 0 27 254 84 0 142 254 85 0 36 255 66 0 225 255 50 0 190 0 54 0 160 1 52 0 89 2 16 0 168 2 202 255 121 2 103 255 253 1 245 254 83 1 148 254 137 0 107 254 217 255 125 254 111 255 173 254 30 255 238 254 195 254 66 255 130 254 151 255 141 254 226 255 241 254 46 0 134 255 115 0 32 0 135 0 191 0 90 0 94 1 24 0 237 1 248 255 123 2 252 255 5 3 249 255 68 3 227 255 12 3 193 255 115 2 119 255 142 1 255 254 107 0 160 254 79 255 139 254 135 254 182 254 20 254 24 255 198 253 172 255 146 253 66 0 152 253 169 0 209 253 233 0 31 254 36 1 147 254 101 1 51 255 153 1 218 255 192 1 126 0 236 1 35 1 25 2 178 1 33 2 30 2 227 1 94 2 96 1 64 2 182 0 157 1 25 0 147 0 183 255 128 255 144 255 193 254 126 255 86 254 116 255 249 253 143 255 171 253 213 255 157 253 43 0 184 253 127 0 219 253 184 0 36 254 193 0 158 254 185 0 66 255 196 0 23 0 217 0 0 1 239 0 199 1 4 1 87 2 252 0 157 2 195 0 128 2 98 0 242 1 237 255 249 0 122 255 208 255 36 255 223 254 248 254 80 254 0 255 245 253 58 255 184 253 134 255 182 253 186 255 254 253 188 255 108 254 140 255 224 254 78 255 113 255 25 255 49 0 241 254 254 0 0 255 193 1 79 255 124 2 139 255 7 3 139 255 44 3 119 255 242 2 69 255 96 2 220 254 100 1 112 254 37 0 61 254 18 255 84 254 111 254 173 254 27 254 27 255 224 253 124 255 206 253 220 255 0 254 51 0 95 254 111 0 215 254 155 0 111 255 169 0 32 0 136 0 213 0 103 0 122 1 116 0 6 2 165 0 132 2 213 0 221 2 208 0 196 2 125 0 37 2 6 0 48 1 146 255 13 0 45 255 0 255 244 254 90 254 236 254 6 254 3 255 192 253 67 255 143 253 146 255 134 253 186 255 150 253 198 255 207 253 212 255 62 254 196 255 208 254 148 255 126 255 126 255 72 0 169 255 27 1 11 0 228 1 113 0 136 2 161 0 218 2 146 0 195 2 89 0 54 2 10 0 53 1 200 255 10 0 164 255 31 255 137 255 153 254 128 255 82 254 175 255 43 254 16 0 53 254 108 0 109 254 150 0 176 254 149 0 1 255 133 0 137 255 108 0 73 0 86 0 33 1 108 0 1 2 171 0 199 2 219 0 72 3 234 0 100 3 225 0 1 3 176 0 42 2 89 0 21 1 2 0 241 255 208 255 242 254 214 255 76 254 2 0 238 253 60 0 171 253 154 0 155 253 41 1 204 253 174 1 35 254 239 1 164 254 232 1 81 255 188 1 254 255 161 1 177 0 184 1 134 1 224 1 91 2 231 1 245 2 186 1 44 3 92 1 233 2 207 0 56 2 39 0 54 1 133 255 253 255 0 255 219 254 157 254 18 254 109 254 134 253 128 254 15 253 184 254 200 252 232 254 207 252 8 255 30 253 20 255 151 253 251 254 18 254 206 254 145 254 181 254 68 255 207 254 57 0 32 255 65 1 124 255 27 2 176 255 152 2 183 255 177 2 142 255 114 2 39 255 210 1 165 254 228 0 77 254 255 255 55 254 100 255 64 254 244 254 84 254 141 254 148 254 84 254 17 255 92 254 143 255 133 254 210 255 190 254 230 255 23 255 213 255 156 255 169 255 83 0 166 255 50 1 225 255 10 2 34 0 176 2 67 0 8 3 64 0 238 2 20 0 84 2 216 255 92 1 141 255 44 0 38 255 6 255 216 254 58 254 217 254 190 253 17 255 94 253 107 255 52 253 246 255 90 253 161 0 169 253 70 1 5 254 188 1 118 254 225 1 1 255 202 1 178 255 170 1 141 0 161 1 111 1 196 1 66 2 244 1 232 2 232 1 24 3 148 1 184 2 26 1 248 1 133 0 249 0 236 255 213 255 129 255 223 254 74 255 85 254 63 255 23 254 106 255 243 253 186 255 232 253 11 0 249 253 53 0 18 254 38 0 61 254 253 255 158 254 232 255 59 255 244 255 13 0 41 0 2 1 115 0 229 1 168 0 137 2 201 0 236 2 218 0 247 2 173 0 130 2 76 0 171 1 249 255 180 0 194 255 204 255 172 255 38 255 198 255 205 254 239 255 150 254 9 0 134 254 42 0 194 254 72 0 41 255 68 0 139 255 22 0 240 255 185 255 96 0 95 255 222 0 88 255 116 1 146 255 17 2 193 255 142 2 218 255 214 2 228 255 209 2 197 255 89 2 111 255 99 1 244 254 41 0 144 254 17 255 140 254 85 254 223 254 223 253 76 255 148 253 181 255 130 253 23 0 173 253 108 0 255 253 172 0 102 254 196 0 229 254 186 0 132 255 184 0 62 0 211 0 255 0 13 1 194 1 97 1 130 2 164 1 20 3 152 1 50 3 44 1 185 2 107 0 189 1 131 255 119 0 190 254 48 255 71 254 41 254 28 254 119 253 43 254 3 253 105 254 207 252 226 254 233 252 127 255 46 253 228 255 112 253 236 255 192 253 216 255 59 254 206 255 234 254 212 255 221 255 0 0 241 0 67 0 214 1 117 0 105 2 153 0 174 2 168 0 147 2 120 0 13 2 13 0 42 1 145 255 17 0 53 255 34 255 26 255 160 254 46 255 96 254 80 255 49 254 151 255 37 254 254 255 72 254 72 0 155 254 94 0 32 255 79 0 196 255 33 0 117 0 252 255 59 1 16 0 16 2 79 0 220 2 146 0 126 3 185 0 183 3 177 0 108 3 140 0 192 2 96 0 201 1 38 0 165 0 224 255 166 255 177 255 232 254 168 255 56 254 198 255 156 253 24 0 94 253 150 0 132 253 15 1 205 253 96 1 22 254 132 1 118 254 129 1 13 255 119 1 231 255 154 1 254 0 221 1 17 2 245 1 212 2 214 1 54 3 167 1 63 3 81 1 198 2 178 0 191 1 235 255 108 0 54 255 42 255 182 254 59 254 102 254 161 253 47 254 41 253 31 254 215 252 82 254 212 252 161 254 1 253 211 254 65 253 223 254 180 253 202 254 91 254 178 254 33 255 230 254 33 0 106 255 83 1 230 255 99 2 40 0 28 3 55 0 112 3 32 0 67 3 239 255 155 2 157 255 175 1 39 255 189 0 179 254 235 255 104 254 55 255 95 254 150 254 177 254 35 254 62 255 251 253 181 255 18 254 240 255 80 254 236 255 167 254 191 255 40 255 161 255 234 255 170 255 205 0 207 255 160 1 26 0 94 2 104 0 237 2 113 0 10 3 62 0 181 2 250 255 11 2 151 255 252 0 33 255 189 255 220 254 207 254 225 254 77 254 36 255 238 253 152 255 165 253 38 0 148 253 180 0 179 253 37 1 238 253 97 1 65 254 120 1 177 254 134 1 81 255 127 1 43 0 107 1 31 1 110 1 248 1 125 1 142 2 123 1 195 2 101 1 147 2 48 1 11 2 205 0 40 1 55 0 1 0 133 255 252 254 254 254 87 254 208 254 220 253 212 254 104 253 251 254 40 253 92 255 40 253 206 255 79 253 17 0 159 253 43 0 33 254 61 0 206 254 84 0 177 255 135 0 191 0 207 0 188 1 21 1 134 2 88 1 24 3 129 1 65 3 83 1 213 2 192 0 242 1 14 0 224 0 146 255 237 255 111 255 80 255 122 255 3 255 129 255 211 254 150 255 167 254 223 255 147 254 65 0 176 254 120 0 237 254 80 0 53 255 213 255 144 255 93 255 25 0 45 255 216 0 49 255 175 1 64 255 104 2 89 255 200 2 116 255 187 2 105 255 71 2 41 255 119 1 213 254 115 0 169 254 142 255 188 254 255 254 249 254 171 254 78 255 103 254 199 255 67 254 80 0 86 254 172 0 135 254 197 0 195 254 190 0 44 255 176 0 217 255 154 0 170 0 150 0 119 1 196 0 57 2 24 1 233 2 96 1 97 3 114 1 122 3 70 1 41 3 228 0 99 2 88 0 30 1 190 255 167 255 54 255 126 254 209 254 180 253 164 254 7 253 193 254 126 252 19 255 76 252 93 255 92 252 114 255 127 252 90 255 195 252 66 255 73 253 64 255 18 254 87 255 22 255 155 255 60 0 27 0 86 1 168 0 45 2 4 1 166 2 28 1 176 2 245 0 64 2 151 0 107 1 10 0 119 0 112 255 172 255 10 255 20 255 250 254 145 254 38 255 43 254 113 255 7 254 213 255 51 254 50 0 158 254 106 0 48 255 125 0 214 255 95 0 132 0 25 0 58 1 231 255 255 1 236 255 203 2 26 0 119 3 87 0 191 3 115 0 116 3 73 0 182 2 3 0 193 1 206 255 174 0 169 255 169 255 159 255 237 254 198 255 113 254 34 0 24 254 180 0 242 253 76 1 254 253 155 1 31 254 152 1 91 254 105 1 185 254 40 1 51 255 9 1 238 255 49 1 247 0 108 1 255 1 137 1 196 2 142 1 65 3 102 1 91 3 240 0 246 2 69 0 32 2 144 255 254 0 226 254 208 255 71 254 219 254 213 253 30 254 166 253 118 253 200 253 251 252 35 254 193 252 139 254 169 252 221 254 177 252 251 254 242 252 235 254 104 253 238 254 34 254 63 255 58 255 202 255 132 0 65 0 170 1 115 0 128 2 110 0 241 2 76 0 220 2 8 0 66 2 159 255 89 1 53 255 113 0 247 254 198 255 253 254 67 255 62 255 187 254 161 255 67 254 252 255 2 254 40 0 237 253 61 0 13 254 102 0 119 254 129 0 12 255 96 0 174 255 65 0 109 0 98 0 64 1 150 0 1 2 195 0 152 2 233 0 224 2 211 0 170 2 107 0 244 1 244 255 243 0 162 255 247 255 118 255 60 255 96 255 180 254 79 255 55 254 98 255 211 253 197 255 160 253 77 0 160 253 163 0 206 253 188 0 31 254 174 0 137 254 143 0 31 255 143 0 239 255 184 0 234 0 215 0 232 1 229 0 177 2 13 1 20 3 62 1 18 3 47 1 181 2 191 0 246 1 22 0 242 0 97 255 245 255 194 254 56 255 103 254 192 254 100 254 105 254 139 254 25 254 191 254 220 253 14 255 199 253 102 255 230 253 159 255 74 254 206 255 240 254 20 0 198 255 110 0 189 0 223 0 176 1 104 1 126 2 217 1 24 3 242 1 79 3 171 1 233 2 53 1 255 1 196 0 231 0 111 0 236 255 65 0 70 255 55 0 236 254 66 0 161 254 99 0 100 254 152 0 80 254 187 0 77 254 168 0 88 254 102 0 150 254 2 0 15 255 158 255 200 255 107 255 197 0 107 255 202 1 115 255 145 2 112 255 247 2 107 255 219 2 105 255 72 2 87 255 125 1 26 255 155 0 199 254 185 255 174 254 18 255 228 254 176 254 48 255 103 254 109 255 47 254 162 255 29 254 213 255 59 254 251 255 141 254 3 0 7 255 236 255 157 255 214 255 92 0 229 255 62 1 37 0 21 2 121 0 191 2 178 0 36 3 192 0 38 3 189 0 197 2 163 0 17 2 79 0 17 1 205 255 238 255 90 255 239 254 35 255 44 254 29 255 135 253 23 255 242 252 10 255 139 252 39 255 108 252 108 255 131 252 151 255 192 252 141 255 50 253 104 255 224 253 99 255 194 254 178 255 210 255 49 0 241 0 150 0 221 1 228 0 104 2 34 1 129 2 25 1 35 2 192 0 108 1 70 0 155 0 185 255 223 255 69 255 68 255 41 255 191 254 98 255 84 254 190 255 15 254 26 0 242 253 99 0 254 253 147 0 74 254 144 0 224 254 47 0 176 255 163 255 166 0 100 255 167 1 131 255 145 2 185 255 63 3 224 255 139 3 238 255 87 3 241 255 183 2 1 0 222 1 6 0 250 0 235 255 54 0 224 255 162 255 253 255 30 255 57 0 155 254 160 0 64 254 19 1 44 254 60 1 66 254 15 1 97 254 194 0 153 254 128 0 15 255 117 0 209 255 177 0 194 0 10 1 195 1 86 1 183 2 135 1 91 3 137 1 99 3 66 1 212 2 163 0 240 1 206 255 220 0 16 255 186 255 133 254 198 254 16 254 10 254 197 253 104 253 208 253 244 252 21 254 201 252 89 254 195 252 132 254 215 252 157 254 41 253 185 254 196 253 246 254 149 254 90 255 157 255 220 255 195 0 104 0 218 1 200 0 181 2 211 0 41 3 157 0 30 3 61 0 169 2 199 255 244 1 109 255 51 1 88 255 147 0 119 255 17 0 162 255 153 255 214 255 52 255 21 0 223 254 81 0 149 254 122 0 119 254 147 0 170 254 164 0 20 255 176 0 142 255 196 0 18 0 215 0 177 0 213 0 111 1 199 0 23 2 206 0 95 2 215 0 48 2 182 0 149 1 100 0 175 0 0 0 187 255 178 255 235 254 152 255 65 254 172 255 189 253 219 255 105 253 40 0 61 253 142 0 65 253 228 0 134 253 2 1 252 253 223 0 134 254 152 0 38 255 106 0 226 255 118 0 189 0 179 0 184 1 3 1 178 2 63 1 85 3 65 1 108 3 10 1 23 3 173 0 120 2 40 0 146 1 125 255 138 0 197 254 156 255 46 254 206 254 232 253 14 254 248 253 106 253 47 254 251 252 106 254 201 252 174 254 228 252 255 254 82 253 83 255 254 253 180 255 223 254 60 0 243 255 228 0 31 1 110 1 46 2 162 1 233 2 150 1 44 3 109 1 242 2 17 1 71 2 139 0 92 1 27 0 139 0 197 255 251 255 109 255 128 255 55 255 3 255 64 255 158 254 102 255 90 254 143 255 62 254 175 255 98 254 168 255 193 254 122 255 75 255 73 255 4 0 45 255 223 0 50 255 187 1 86 255 123 2 114 255 246 2 115 255 10 3 106 255 182 2 70 255 5 2 249 254 17 1 195 254 29 0 226 254 93 255 51 255 197 254 118 255 68 254 178 255 224 253 7 0 170 253 93 0 183 253 124 0 14 254 91 0 147 254 50 0 52 255 48 0 251 255 89 0 221 0 140 0 179 1 171 0 96 2 185 0 221 2 203 0 19 3 225 0 211 2 220 0 21 2 170 0 21 1 101 0 20 0 44 0 43 255 3 0 104 254 226 255 201 253 207 255 58 253 209 255 196 252 234 255 141 252 10 0 147 252 29 0 197 252 24 0 45 253 247 255 205 253 195 255 154 254 173 255 135 255 234 255 110 0 91 0 42 1 179 0 180 1 211 0 239 1 209 0 176 1 195 0 27 1 172 0 121 0 121 0 233 255 41 0 116 255 240 255 21 255 3 0 184 254 78 0 119 254 148 0 113 254 168 0 150 254 125 0 225 254 40 0 106 255 197 255 25 0 109 255 209 0 66 255 163 1 73 255 134 2 91 255 57 3 105 255 143 3 128 255 133 3 138 255 39 3 121 255 132 2 110 255 174 1 138 255 220 0 205 255 73 0 44 0 221 255 134 0 101 255 214 0 246 254 45 1 182 254 109 1 163 254 97 1 183 254 28 1 247 254 205 0 104 255 125 0 8 0 67 0 195 0 56 0 118 1 66 0 26 2 74 0 156 2 93 0 203 2 99 0 146 2 51 0 4 2 210 255 48 1 99 255 60 0 249 254 99 255 160 254 181 254 112 254 36 254 127 254 179 253 198 254 90 253 17 255 14 253 47 255 230 252 32 255 5 253 19 255 105 253 52 255 9 254 139 255 236 254 239 255 2 0 48 0 19 1 83 0 243 1 126 0 134 2 163 0 179 2 142 0 109 2 55 0 209 1 224 255 29 1 199 255 136 0 212 255 22 0 192 255 146 255 144 255 232 254 155 255 80 254 243 255 252 253 64 0 223 253 76 0 231 253 59 0 36 254 44 0 159 254 38 0 78 255 53 0 31 0 77 0 245 0 81 0 179 1 66 0 63 2 42 0 115 2 12 0 45 2 228 255 123 1 174 255 153 0 125 255 189 255 112 255 1 255 122 255 106 254 142 255 252 253 223 255 196 253 114 0 193 253 219 0 209 253 232 0 237 253 216 0 52 254 198 0 174 254 165 0 78 255 150 0 28 0 189 0 11 1 9 1 228 1 76 1 130 2 82 1 210 2 252 0 182 2 105 0 43 2 197 255 103 1 33 255 161 0 156 254 232 255 91 254 51 255 89 254 129 254 129 254 239 253 201 254 141 253 29 255 75 253 99 255 44 253 153 255 82 253 206 255 210 253 26 0 171 254 147 0 194 255 42 1 228 0 157 1 221 1 191 1 139 2 176 1 215 2 157 1 204 2 123 1 119 2 56 1 224 1 230 0 50 1 137 0 155 0 21 0 2 0 176 255 95 255 142 255 227 254 152 255 152 254 157 255 103 254 161 255 100 254 150 255 165 254 73 255 29 255 213 254 196 255 169 254 142 0 230 254 99 1 49 255 40 2 70 255 181 2 44 255 234 2 238 254 193 2 159 254 61 2 112 254 123 1 122 254 184 0 170 254 12 0 238 254 108 255 48 255 222 254 100 255 95 254 176 255 249 253 37 0 220 253 130 0 20 254 159 0 139 254 159 0 54 255 158 0 243 255 160 0 155 0 172 0 71 1 169 0 6 2 135 0 168 2 113 0 13 3 113 0 24 3 75 0 146 2 247 255 144 1 165 255 114 0 112 255 117 255 105 255 175 254 127 255 22 254 124 255 128 253 111 255 238 252 166 255 141 252 24 0 107 252 103 0 132 252 108 0 225 252 84 0 122 253 94 0 68 254 158 0 44 255 235 0 9 0 40 1 196 0 88 1 89 1 102 1 165 1 76 1 134 1 37 1 26 1 227 0 143 0 127 0 255 255 71 0 137 255 95 0 59 255 120 0 4 255 105 0 210 254 89 0 169 254 91 0 160 254 85 0 206 254 45 0 53 255 219 255 197 255 141 255 117 0 118 255 52 1 146 255 232 1 185 255 118 2 188 255 209 2 135 255 228 2 69 255 149 2 42 255 242 1 37 255 56 1 29 255 140 0 35 255 241 255 65 255 110 255 99 255 3 255 147 255 171 254 245 255 121 254 125 0 125 254 225 0 166 254 226 0 239 254 145 0 94 255 47 0 237 255 235 255 144 0 202 255 62 1 190 255 230 1 202 255 121 2 228 255 198 2 212 255 149 2 123 255 254 1 10 255 64 1 168 254 110 0 85 254 175 255 42 254 49 255 55 254 195 254 104 254 43 254 184 254 159 253 21 255 81 253 74 255 51 253 93 255 71 253 139 255 161 253 217 255 63 254 43 0 22 255 127 0 8 0 193 0 229 0 223 0 146 1 248 0 0 2 31 1 25 2 48 1 227 1 15 1 128 1 222 0 7 1 227 0 142 0 16 1 38 0 3 1 189 255 176 0 70 255 125 0 232 254 148 0 173 254 192 0 123 254 202 0 105 254 151 0 157 254 82 0 9 255 77 0 155 255 141 0 89 0 199 0 38 1 214 0 212 1 203 0 73 2 180 0 103 2 149 0 32 2 89 0 136 1 248 255 182 0 156 255 207 255 106 255 21 255 93 255 150 254 123 255 50 254 213 255 238 253 73 0 199 253 157 0 157 253 185 0 134 253 163 0 182 253 94 0 46 254 254 255 225 254 203 255 202 255 237 255 202 0 50 0 176 1 91 0 81 2 86 0 139 2 14 0 106 2 126 255 255 1 230 254 81 1 143 254 152 0 126 254 7 0 141 254 118 255 157 254 218 254 189 254 110 254 20 255 62 254 142 255 36 254 239 255 37 254 23 0 76 254 22 0 154 254 27 0 42 255 83 0 248 255 185 0 227 0 29 1 212 1 88 1 146 2 99 1 222 2 87 1 195 2 61 1 89 2 231 0 156 1 89 0 201 0 237 255 55 0 188 255 207 255 148 255 91 255 117 255 242 254 111 255 165 254 96 255 88 254 57 255 34 254 9 255 67 254 217 254 186 254 187 254 86 255 188 254 14 0 213 254 246 0 11 255 229 1 79 255 144 2 107 255 217 2 76 255 203 2 30 255 105 2 1 255 189 1 249 254 238 0 3 255 44 0 33 255 141 255 91 255 6 255 173 255 140 254 251 255 49 254 52 0 12 254 82 0 31 254 101 0 96 254 148 0 209 254 212 0 113 255 235 0 51 0 213 0 249 0 187 0 177 1 170 0 89 2 167 0 216 2 169 0 239 2 135 0 131 2 72 0 193 1 26 0 236 0 6 0 36 0 255 255 113 255 17 0 210 254 60 0 69 254 124 0 210 253 194 0 121 253 248 0 65 253 25 1 71 253 35 1 140 253 255 0 242 253 204 0 129 254 216 0 83 255 31 1 68 0 104 1 13 1 171 1 154 1 225 1 226 1 218 1 195 1 138 1 73 1 20 1 179 0 149 0 36 0 46 0 164 255 6 0 64 255 21 0 247 254 39 0 181 254 23 0 115 254 243 255 68 254 194 255 67 254 111 255 136 254 248 254 16 255 144 254 181 255 113 254 105 0 168 254 56 1 9 255 253 1 89 255 124 2 138 255 175 2 163 255 155 2 152 255 32 2 103 255 83 1 54 255 139 0 37 255 243 255 62 255 124 255 125 255 37 255 213 255 227 254 56 0 176 254 148 0 153 254 195 0 156 254 176 0 173 254 109 0 228 254 23 0 93 255 208 255 13 0 168 255 208 0 132 255 128 1 84 255 3 2 45 255 74 2 1 255 58 2 177 254 202 1 87 254 39 1 21 254 127 0 231 253 229 255 215 253 96 255 234 253 221 254 3 254 75 254 48 254 208 253 143 254 136 253 241 254 92 253 24 255 87 253 27 255 163 253 52 255 51 254 118 255 232 254 207 255 172 255 18 0 100 0 35 0 15 1 45 0 169 1 80 0 248 1 104 0 214 1 92 0 110 1 82 0 223 0 114 0 71 0 177 0 223 255 230 0 157 255 253 0 81 255 14 1 19 255 49 1 1 255 75 1 248 254 58 1 234 254 255 0 253 254 173 0 64 255 108 0 179 255 101 0 86 0 141 0 8 1 187 0 163 1 223 0 7 2 234 0 7 2 211 0 169 1 166 0 33 1 105 0 122 0 31 0 183 255 246 255 24 255 14 0 182 254 70 0 95 254 143 0 23 254 244 0 252 253 66 1 245 253 63 1 255 253 253 0 67 254 172 0 191 254 108 0 91 255 88 0 16 0 106 0 206 0 115 0 139 1 82 0 53 2 20 0 135 2 209 255 87 2 134 255 211 1 37 255 35 1 199 254 94 0 160 254 184 255 182 254 59 255 228 254 189 254 30 255 79 254 116 255 18 254 222 255 238 253 53 0 227 253 88 0 24 254 74 0 124 254 42 0 253 254 56 0 177 255 156 0 147 0 31 1 133 1 105 1 96 2 125 1 225 2 127 1 226 2 90 1 135 2 253 0 243 1 150 0 64 1 78 0 166 0 37 0 68 0 20 0 247 255 35 0 180 255 80 0 131 255 116 0 84 255 99 0 40 255 49 0 52 255 245 255 137 255 152 255 3 0 47 255 148 0 247 254 62 1 225 254 239 1 197 254 142 2 176 254 237 2 160 254 211 2 123 254 69 2 88 254 134 1 79 254 198 0 98 254 24 0 143 254 124 255 189 254 232 254 230 254 113 254 72 255 58 254 233 255 37 254 109 0 9 254 172 0 10 254 197 0 72 254 188 0 185 254 171 0 88 255 175 0 27 0 155 0 219 0 74 0 135 1 247 255 15 2 210 255 58 2 184 255 241 1 122 255 90 1 49 255 166 0 35 255 251 255 110 255 104 255 225 255 237 254 61 0 147 254 129 0 86 254 220 0 17 254 78 1 194 253 135 1 145 253 81 1 148 253 242 0 213 253 200 0 87 254 217 0 252 254 248 0 176 255 10 1 102 0 252 0 242 0 205 0 53 1 141 0 56 1 52 0 237 0 200 255 87 0 134 255 191 255 145 255 87 255 196 255 27 255 249 255 5 255 50 0 4 255 116 0 245 254 177 0 228 254 190 0 225 254 124 0 219 254 253 255 235 254 123 255 60 255 22 255 190 255 215 254 92 0 196 254 30 1 220 254 217 1 5 255 76 2 50 255 108 2 97 255 70 2 113 255 204 1 73 255 17 1 35 255 93 0 64 255 227 255 146 255 156 255 5 0 109 255 150 0 75 255 32 1 50 255 105 1 25 255 85 1 12 255 227 0 43 255 62 0 132 255 189 255 3 0 136 255 160 0 132 255 88 1 131 255 8 2 101 255 118 2 41 255 125 2 241 254 34 2 198 254 149 1 146 254 248 0 91 254 87 0 68 254 202 255 93 254 107 255 161 254 37 255 246 254 212 254 75 255 130 254 155 255 73 254 202 255 57 254] \ No newline at end of file diff --git a/testdata/audio-s16le.pcm b/testdata/audio-s16le.pcm new file mode 100644 index 0000000..64c3c70 Binary files /dev/null and b/testdata/audio-s16le.pcm differ