WiP: decoder interface, libdav1d

This commit is contained in:
DataHoarder 2022-11-10 11:38:55 +01:00
parent ac6a6c11d7
commit 6722babcac
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
4 changed files with 243 additions and 0 deletions

View file

@ -18,6 +18,7 @@ type SubsamplingScheme struct {
B byte
}
// TODO: add color primaries
type ColorFormat struct {
Subsampling SubsamplingScheme
BitDepth byte

1
decoder/decoder.go Normal file
View file

@ -0,0 +1 @@
package decoder

View file

@ -0,0 +1,234 @@
package libdav1d
/*
#cgo pkg-config: dav1d
#include <dav1d/dav1d.h>
*/
import "C"
import (
"errors"
"fmt"
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
"git.gammaspectra.live/S.O.N.G/Ignite/utilities/ivfreader"
"io"
"sync/atomic"
"unsafe"
)
type Decoder struct {
r *ivfreader.IVFReader
h *ivfreader.IVFFileHeader
cleaned atomic.Bool
settings C.Dav1dSettings
ctx *C.Dav1dContext
picture C.Dav1dPicture
data C.Dav1dData
}
var dav1dVersion = fmt.Sprintf("dav1d %s", C.GoString(C.dav1d_version()))
const (
ErrEAGAIN = 11
)
func Version() string {
return dav1dVersion
}
func NewDecoder(r io.Reader, settings map[string]any) (d *Decoder, err error) {
d = &Decoder{}
if d.r, d.h, err = ivfreader.NewWith(r); err != nil {
return nil, err
}
C.dav1d_default_settings(&d.settings)
if ret := C.dav1d_open(&d.ctx, &d.settings); ret != 0 {
return nil, fmt.Errorf("error %d", ret)
}
if err = d.readIvf(); err != nil {
return nil, err
}
return d, nil
}
func (d *Decoder) readIvf() error {
if buf, header, err := d.r.ParseNextFrame(); err != nil {
return err
} else {
if ptr := C.dav1d_data_create(&d.data, C.size_t(len(buf))); ptr == nil {
return errors.New("could not allocate data")
} else {
copy(unsafe.Slice((*byte)(ptr), len(buf)), buf)
d.data.m.timestamp = C.long(header.Timestamp)
}
}
return nil
}
func (d *Decoder) decodeFrame() C.int {
var res C.int
if res = C.dav1d_send_data(d.ctx, &d.data); res < 0 {
if res != -ErrEAGAIN {
return res
}
}
if res = d.flushPicture(); res < 0 {
if res != -ErrEAGAIN {
return res
}
}
if d.data.sz == 0 {
if err := d.readIvf(); err != nil {
if err == io.EOF {
//try flush?
if res = d.flushPicture(); res != -ErrEAGAIN {
return res
}
}
res = -1
}
}
return res
}
func (d *Decoder) flushPicture() C.int {
//todo memset picture to 0?
res := C.dav1d_get_picture(d.ctx, &d.picture)
return res
}
func (d *Decoder) Close() {
if d.data.sz > 0 {
C.dav1d_data_unref(&d.data)
}
//TODO: close other context
}
const (
planeY = 0
planeU = 1
planeV = 2
)
func (d *Decoder) pictureToFrame() (frame.Frame, error) {
bitDepth := int(d.picture.p.bpc)
height := int(d.picture.p.h)
width := int(d.picture.p.w)
chromaWidth := 0
chromaHeight := 0
var colorFormat colorformat.ColorFormat
switch d.picture.p.layout {
case C.DAV1D_PIXEL_LAYOUT_I400:
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 0, B: 0}
case C.DAV1D_PIXEL_LAYOUT_I420:
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 0}
case C.DAV1D_PIXEL_LAYOUT_I422:
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 2}
case C.DAV1D_PIXEL_LAYOUT_I444:
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 4, B: 4}
}
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
ssVer := 0
if d.picture.p.layout == C.DAV1D_PIXEL_LAYOUT_I420 {
ssVer = 1
}
ssHor := 0
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I444 {
ssHor = 1
}
chromaWidth = (width + ssHor) >> ssHor
chromaHeight = (height + ssVer) >> ssVer
}
colorFormat.BitDepth = byte(bitDepth)
defer C.dav1d_picture_unref(&d.picture)
if bitDepth > 8 { //16-bit
//TODO: check reserve
buf := make([]byte, 0, height*width*2+chromaHeight*chromaWidth*2*2)
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), height*width*2)
for y := 0; y < height; y++ {
buf = append(buf, yData[:width*2]...)
yData = yData[d.picture.stride[0]:]
}
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
uData := unsafe.Slice((*byte)(d.picture.data[planeU]), chromaHeight*chromaWidth*2)
vData := unsafe.Slice((*byte)(d.picture.data[planeV]), chromaHeight*chromaWidth*2)
//TODO: check sizes!
for y := 0; y < chromaHeight; y++ {
buf = append(buf, uData[:chromaWidth*2]...)
uData = uData[d.picture.stride[1]:]
}
for y := 0; y < chromaHeight; y++ {
buf = append(buf, vData[:chromaWidth*2]...)
vData = vData[d.picture.stride[1]:]
}
}
return frame.NewUint16FrameFromBytes(colorFormat, width, height, buf)
} else {
//TODO: check reserve
buf := make([]byte, 0, height*width+chromaHeight*chromaWidth*2)
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), height*width)
for y := 0; y < height; y++ {
buf = append(buf, yData[:width]...)
yData = yData[d.picture.stride[0]:]
}
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
uData := unsafe.Slice((*byte)(d.picture.data[planeU]), chromaHeight*chromaWidth)
vData := unsafe.Slice((*byte)(d.picture.data[planeV]), chromaHeight*chromaWidth)
//TODO: check sizes!
for y := 0; y < chromaHeight; y++ {
buf = append(buf, uData[:chromaWidth]...)
uData = uData[d.picture.stride[1]:]
}
for y := 0; y < chromaHeight; y++ {
buf = append(buf, vData[:chromaWidth]...)
vData = vData[d.picture.stride[1]:]
}
}
return frame.NewUint8FrameFromBytes(colorFormat, width, height, buf)
}
}
func (d *Decoder) Decode() (frame.Frame, error) {
var ret C.int
for {
if ret = d.decodeFrame(); ret == -ErrEAGAIN {
continue
}
if ret == 0 {
return d.pictureToFrame()
}
if ret == -1 {
return nil, io.EOF
}
return nil, fmt.Errorf("error %d", ret)
}
}

View file

@ -0,0 +1,7 @@
package libdav1d
import "testing"
func TestVersion(t *testing.T) {
t.Logf("dav1d version: %s", Version())
}