278 lines
8.1 KiB
Go
278 lines
8.1 KiB
Go
//go:build cgo && !disable_library_libvmaf
|
|
|
|
package libvmaf
|
|
|
|
/*
|
|
#cgo pkg-config: libvmaf
|
|
#include "libvmaf.h"
|
|
#include <stdlib.h>
|
|
|
|
*/
|
|
import "C"
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
|
"unsafe"
|
|
)
|
|
|
|
var vmafVersion = "vmaf " + C.GoString(C.vmaf_version())
|
|
|
|
func Version() string {
|
|
return vmafVersion
|
|
}
|
|
|
|
type VMAF struct {
|
|
context *C.VmafContext
|
|
model *C.VmafModel
|
|
}
|
|
|
|
type ModelFlags uint64
|
|
|
|
const (
|
|
ModelFlagsDefault ModelFlags = C.VMAF_MODEL_FLAGS_DEFAULT
|
|
ModelFlagDisableClip ModelFlags = C.VMAF_MODEL_FLAG_DISABLE_CLIP
|
|
ModelFlagEnableTransform ModelFlags = C.VMAF_MODEL_FLAG_ENABLE_TRANSFORM
|
|
ModelFlagDisableTransform ModelFlags = C.VMAF_MODEL_FLAG_DISABLE_TRANSFORM
|
|
)
|
|
|
|
type ModelConfiguration struct {
|
|
Name string
|
|
Version string
|
|
Flags ModelFlags
|
|
}
|
|
|
|
func NewModelDefault() *ModelConfiguration {
|
|
return &ModelConfiguration{
|
|
Name: "vmaf",
|
|
Version: "vmaf_v0.6.1",
|
|
Flags: ModelFlagsDefault,
|
|
}
|
|
}
|
|
|
|
func New(subsample uint, model *ModelConfiguration) (*VMAF, error) {
|
|
v := &VMAF{}
|
|
|
|
var cfg C.VmafConfiguration
|
|
cfg.n_threads = 0 //runtime.NumCPU()
|
|
cfg.n_subsample = C.uint(subsample)
|
|
cfg.cpumask = 0
|
|
cfg.log_level = C.VMAF_LOG_LEVEL_NONE
|
|
|
|
if ret := C.vmaf_init(&v.context, cfg); ret != 0 {
|
|
return nil, fmt.Errorf("vmaf_init error %d", ret)
|
|
} else {
|
|
var modelConfig C.VmafModelConfig
|
|
modelConfig.name = C.CString(model.Name)
|
|
defer C.free(unsafe.Pointer(modelConfig.name))
|
|
modelConfig.flags = C.uint64_t(model.Flags)
|
|
|
|
modelVersion := C.CString(model.Version)
|
|
defer C.free(unsafe.Pointer(modelVersion))
|
|
|
|
if ret = C.vmaf_model_load(&v.model, &modelConfig, modelVersion); ret != 0 {
|
|
v.Close()
|
|
return nil, fmt.Errorf("vmaf_model_load error %d", ret)
|
|
}
|
|
|
|
if ret = C.vmaf_use_features_from_model(v.context, v.model); ret != 0 {
|
|
return nil, fmt.Errorf("vmaf_use_features_from_model error %d", ret)
|
|
}
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
func (v *VMAF) allocatePicture(properties frame.Properties) *C.VmafPicture {
|
|
//todo: reuse these pictures
|
|
pixFmt := uint32(C.VMAF_PIX_FMT_YUV420P)
|
|
switch true {
|
|
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 4 && properties.ColorSpace.ChromaSampling.B == 4:
|
|
pixFmt = C.VMAF_PIX_FMT_YUV444P
|
|
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 2:
|
|
pixFmt = C.VMAF_PIX_FMT_YUV422P
|
|
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 0:
|
|
pixFmt = C.VMAF_PIX_FMT_YUV420P
|
|
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 0 && properties.ColorSpace.ChromaSampling.B == 0:
|
|
pixFmt = C.VMAF_PIX_FMT_YUV400P
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
var p C.VmafPicture
|
|
if ret := C.vmaf_picture_alloc(&p, pixFmt, C.uint(properties.ColorSpace.BitDepth), C.uint(properties.Width), C.uint(properties.Height)); ret != 0 {
|
|
return nil
|
|
}
|
|
|
|
return &p
|
|
}
|
|
|
|
func (v *VMAF) deallocatePicture(p *C.VmafPicture) bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
return C.vmaf_picture_unref(p) == 0
|
|
}
|
|
|
|
func (v *VMAF) frameToPicture(f frame.Frame) *C.VmafPicture {
|
|
if f == nil {
|
|
return nil
|
|
}
|
|
if p := v.allocatePicture(f.Properties()); p == nil {
|
|
return nil
|
|
} else {
|
|
//TODO: check validity of lengths
|
|
if f16, ok := f.(frame.TypedFrame[uint16]); ok {
|
|
yPlane := unsafe.Slice((*uint16)(p.data[0]), len(f16.GetNativeLuma()))
|
|
uPlane := unsafe.Slice((*uint16)(p.data[1]), len(f16.GetNativeCb()))
|
|
vPlane := unsafe.Slice((*uint16)(p.data[2]), len(f16.GetNativeCr()))
|
|
copy(yPlane, f16.GetNativeLuma())
|
|
copy(uPlane, f16.GetNativeCb())
|
|
copy(vPlane, f16.GetNativeCr())
|
|
} else if f8, ok := f.(frame.TypedFrame[uint8]); ok {
|
|
yPlane := unsafe.Slice((*uint8)(p.data[0]), len(f8.GetNativeLuma()))
|
|
uPlane := unsafe.Slice((*uint8)(p.data[1]), len(f8.GetNativeCb()))
|
|
vPlane := unsafe.Slice((*uint8)(p.data[2]), len(f8.GetNativeCr()))
|
|
copy(yPlane, f8.GetNativeLuma())
|
|
copy(uPlane, f8.GetNativeCb())
|
|
copy(vPlane, f8.GetNativeCr())
|
|
} else {
|
|
// not supported frame
|
|
v.deallocatePicture(p)
|
|
return nil
|
|
}
|
|
|
|
return p
|
|
}
|
|
}
|
|
|
|
func (v *VMAF) ScoreAtIndex(index uint) (float64, error) {
|
|
var score float64
|
|
if ret := C.vmaf_score_at_index(v.context, v.model, (*C.double)(&score), C.uint(index)); ret != 0 {
|
|
return 0, fmt.Errorf("vmaf_score_at_index error %d", ret)
|
|
}
|
|
|
|
return score, nil
|
|
}
|
|
|
|
func (v *VMAF) FeatureScoreAtIndex(featureName string, index uint) (float64, error) {
|
|
name := C.CString(featureName)
|
|
defer C.free(unsafe.Pointer(name))
|
|
var score float64
|
|
if ret := C.vmaf_feature_score_at_index(v.context, name, (*C.double)(&score), C.uint(index)); ret != 0 {
|
|
return 0, fmt.Errorf("vmaf_feature_score_at_index error %d", ret)
|
|
}
|
|
|
|
return score, nil
|
|
}
|
|
|
|
type PoolingMethod uint32
|
|
|
|
const (
|
|
PoolingMethodUnknown PoolingMethod = C.VMAF_POOL_METHOD_UNKNOWN
|
|
PoolingMethodMinimum PoolingMethod = C.VMAF_POOL_METHOD_MIN
|
|
PoolingMethodMaximum PoolingMethod = C.VMAF_POOL_METHOD_MAX
|
|
PoolingMethodMean PoolingMethod = C.VMAF_POOL_METHOD_MEAN
|
|
PoolingMethodHarmonicMean PoolingMethod = C.VMAF_POOL_METHOD_HARMONIC_MEAN
|
|
PoolingMethodNB PoolingMethod = C.VMAF_POOL_METHOD_NB
|
|
)
|
|
|
|
func (v *VMAF) ScorePooled(method PoolingMethod, indexLow, indexHigh uint) (float64, error) {
|
|
var score float64
|
|
if ret := C.vmaf_score_pooled(v.context, v.model, uint32(method), (*C.double)(&score), C.uint(indexLow), C.uint(indexHigh)); ret != 0 {
|
|
return 0, fmt.Errorf("vmaf_score_at_index error %d", ret)
|
|
}
|
|
|
|
return score, nil
|
|
}
|
|
|
|
// ReadStreams Read a pair of frame.Stream and queues each frame.Frame via ReadPictures for eventual feature extraction, and flushes at the end. Returns the count of frame.Frame used (indexes will be up to count - 1).
|
|
func (v *VMAF) ReadStreams(reference, distorted *frame.Stream) (uint, error) {
|
|
var index uint
|
|
|
|
var refFrame, distFrame frame.Frame
|
|
var refOk, distOk bool
|
|
var err error
|
|
|
|
refChannel, distChannel := reference.Channel(), distorted.Channel()
|
|
for {
|
|
refFrame, refOk = <-refChannel
|
|
distFrame, distOk = <-distChannel
|
|
if !refOk && !distOk {
|
|
//flush
|
|
if err = v.ReadPictures(nil, nil, index); err != nil {
|
|
return index, err
|
|
}
|
|
return index, nil
|
|
} else if !refOk {
|
|
return index, errors.New("reference ended before distorted")
|
|
} else if !distOk {
|
|
return index, errors.New("distorted ended before reference")
|
|
} else {
|
|
if err = v.ReadPictures(refFrame, distFrame, index); err != nil {
|
|
return index, err
|
|
}
|
|
}
|
|
|
|
index++
|
|
}
|
|
}
|
|
|
|
// ReadPictures Read a pair of frame.Frame and queue them for eventual feature extraction.
|
|
// When you're done reading pictures call this function again with both reference and distorted set to nil
|
|
func (v *VMAF) ReadPictures(reference, distorted frame.Frame, index uint) error {
|
|
if reference == nil && distorted == nil {
|
|
//flush
|
|
if ret := C.vmaf_read_pictures(v.context, nil, nil, C.uint(index)); ret != 0 {
|
|
return fmt.Errorf("vmaf_read_pictures error %d", ret)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
ref := v.frameToPicture(reference)
|
|
dist := v.frameToPicture(distorted)
|
|
defer v.deallocatePicture(ref)
|
|
defer v.deallocatePicture(dist)
|
|
|
|
if ref == nil || dist == nil {
|
|
return errors.New("could not allocate pictures")
|
|
}
|
|
if ret := C.vmaf_read_pictures(v.context, ref, dist, C.uint(index)); ret != 0 {
|
|
return fmt.Errorf("vmaf_read_pictures error %d", ret)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (v *VMAF) Close() {
|
|
if v.model != nil {
|
|
C.vmaf_model_destroy(v.model)
|
|
v.model = nil
|
|
}
|
|
if v.context != nil {
|
|
C.vmaf_close(v.context)
|
|
v.context = nil
|
|
}
|
|
}
|
|
|
|
type OutputFormat uint32
|
|
|
|
const (
|
|
OutputFormatNone OutputFormat = C.VMAF_OUTPUT_FORMAT_NONE
|
|
OutputFormatXml OutputFormat = C.VMAF_OUTPUT_FORMAT_XML
|
|
OutputFormatJson OutputFormat = C.VMAF_OUTPUT_FORMAT_JSON
|
|
OutputFormatCsv OutputFormat = C.VMAF_OUTPUT_FORMAT_CSV
|
|
OutputFormatSub OutputFormat = C.VMAF_OUTPUT_FORMAT_SUB
|
|
)
|
|
|
|
func (v *VMAF) Output(path string, format OutputFormat) error {
|
|
cPath := C.CString(path)
|
|
defer C.free(unsafe.Pointer(cPath))
|
|
if ret := C.vmaf_write_output(v.context, cPath, uint32(format)); ret != 0 {
|
|
return fmt.Errorf("vmaf_write_output error %d", ret)
|
|
}
|
|
|
|
return nil
|
|
}
|