WiP: decoder interface, libdav1d
This commit is contained in:
parent
ac6a6c11d7
commit
6722babcac
|
@ -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
1
decoder/decoder.go
Normal file
|
@ -0,0 +1 @@
|
|||
package decoder
|
234
decoder/libdav1d/libdav1d.go
Normal file
234
decoder/libdav1d/libdav1d.go
Normal 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)
|
||||
}
|
||||
}
|
7
decoder/libdav1d/libdav1d_test.go
Normal file
7
decoder/libdav1d/libdav1d_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package libdav1d
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Logf("dav1d version: %s", Version())
|
||||
}
|
Loading…
Reference in a new issue